e027622726179ec964043ce7a4009d6e2a588bb6
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-1999 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #ifdef HAVE_GTK /* whole file */
18
19 #include <stdlib.h>
20
21 #ifdef HAVE_UNISTD_H
22 # include <unistd.h>
23 #endif
24
25 #ifndef VMS
26 # include <pwd.h>               /* for getpwuid() */
27 #else /* VMS */
28 # include "vms-pwd.h"
29 #endif /* VMS */
30
31 #ifdef HAVE_UNAME
32 # include <sys/utsname.h>       /* for uname() */
33 #endif /* HAVE_UNAME */
34
35 #include <stdio.h>
36
37 #include <X11/Xproto.h>         /* for CARD32 */
38 #include <X11/Xatom.h>          /* for XA_INTEGER */
39 #include <X11/Intrinsic.h>
40 #include <X11/StringDefs.h>
41
42 /* We don't actually use any widget internals, but these are included
43    so that gdb will have debug info for the widgets... */
44 #include <X11/IntrinsicP.h>
45 #include <X11/ShellP.h>
46
47 #ifdef HAVE_XMU
48 # ifndef VMS
49 #  include <X11/Xmu/Error.h>
50 # else /* VMS */
51 #  include <Xmu/Error.h>
52 # endif
53 #else
54 # include "xmu.h"
55 #endif
56
57
58
59 #include <gtk/gtk.h>
60
61 extern Display *gdk_display;
62
63 #include "version.h"
64 #include "prefs.h"
65 #include "resources.h"          /* for parse_time() */
66 #include "visual.h"             /* for has_writable_cells() */
67 #include "remote.h"             /* for xscreensaver_command() */
68 #include "usleep.h"
69
70 #include "demo-Gtk-widgets.h"
71
72 #include <stdio.h>
73 #include <string.h>
74 #include <ctype.h>
75
76 #undef countof
77 #define countof(x) (sizeof((x))/sizeof((*x)))
78
79
80 char *progname = 0;
81 char *progclass = "XScreenSaver";
82 XrmDatabase db;
83
84 typedef struct {
85   saver_preferences *a, *b;
86 } prefs_pair;
87
88 static void *global_prefs_pair;  /* I hate C so much... */
89
90 char *blurb (void) { return progname; }
91
92 static char *short_version = 0;
93
94 Atom XA_VROOT;
95 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
96 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
97 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
98
99
100 static void populate_demo_window (GtkWidget *toplevel,
101                                   int which, prefs_pair *pair);
102 static void populate_prefs_page (GtkWidget *top, prefs_pair *pair);
103 static int apply_changes_and_save (GtkWidget *widget);
104 static int maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair);
105
106 \f
107 /* Some random utility functions
108  */
109
110 static GtkWidget *
111 name_to_widget (GtkWidget *widget, const char *name)
112 {
113   while (1)
114     {
115       GtkWidget *parent = (GTK_IS_MENU (widget)
116                            ? gtk_menu_get_attach_widget (GTK_MENU (widget))
117                            : widget->parent);
118       if (parent)
119         widget = parent;
120       else
121         break;
122     }
123   return (GtkWidget *) gtk_object_get_data (GTK_OBJECT (widget), name);
124 }
125
126
127
128 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
129    Takes a scroller, viewport, or list as an argument.
130  */
131 static void
132 ensure_selected_item_visible (GtkWidget *widget)
133 {
134   GtkScrolledWindow *scroller = 0;
135   GtkViewport *vp = 0;
136   GtkList *list_widget = 0;
137   GList *slist;
138   GList *kids;
139   int nkids = 0;
140   GtkWidget *selected = 0;
141   int which = -1;
142   GtkAdjustment *adj;
143   gint parent_h, child_y, child_h, children_h, ignore;
144   double ratio_t, ratio_b;
145
146   if (GTK_IS_SCROLLED_WINDOW (widget))
147     {
148       scroller = GTK_SCROLLED_WINDOW (widget);
149       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
150       list_widget = GTK_LIST (GTK_BIN(vp)->child);
151     }
152   else if (GTK_IS_VIEWPORT (widget))
153     {
154       vp = GTK_VIEWPORT (widget);
155       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
156       list_widget = GTK_LIST (GTK_BIN(vp)->child);
157     }
158   else if (GTK_IS_LIST (widget))
159     {
160       list_widget = GTK_LIST (widget);
161       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
162       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
163     }
164   else
165     abort();
166
167   slist = list_widget->selection;
168   selected = (slist ? GTK_WIDGET (slist->data) : 0);
169   if (!selected)
170     return;
171
172   which = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
173
174   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
175        kids; kids = kids->next)
176     nkids++;
177
178   adj = gtk_scrolled_window_get_vadjustment (scroller);                        
179
180   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
181                            &ignore, &ignore, &ignore, &parent_h, &ignore);
182   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
183                            &ignore, &child_y, &ignore, &child_h, &ignore);
184   children_h = nkids * child_h;
185
186   ratio_t = ((double) child_y) / ((double) children_h);
187   ratio_b = ((double) child_y + child_h) / ((double) children_h);
188
189   if (adj->upper == 0.0)  /* no items in list */
190     return;
191
192   if (ratio_t < (adj->value / adj->upper) ||
193       ratio_b > ((adj->value + adj->page_size) / adj->upper))
194     {
195       double target;
196       int slop = parent_h * 0.75; /* how much to overshoot by */
197
198       if (ratio_t < (adj->value / adj->upper))
199         {
200           double ratio_w = ((double) parent_h) / ((double) children_h);
201           double ratio_l = (ratio_b - ratio_t);
202           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
203           target += slop;
204         }
205       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
206         {
207           target = ratio_t * adj->upper;
208           target -= slop;
209         }
210
211       if (target > adj->upper - adj->page_size)
212         target = adj->upper - adj->page_size;
213       if (target < 0)
214         target = 0;
215
216       gtk_adjustment_set_value (adj, target);
217     }
218 }
219
220
221 static void
222 warning_dialog_dismiss_cb (GtkButton *button, gpointer user_data)
223 {
224   GtkWidget *shell = GTK_WIDGET (user_data);
225   while (shell->parent)
226     shell = shell->parent;
227   gtk_widget_destroy (GTK_WIDGET (shell));
228 }
229
230
231 static void
232 warning_dialog (GtkWidget *parent, const char *message, int center)
233 {
234   char *msg = strdup (message);
235   char *head;
236
237   GtkWidget *dialog = gtk_dialog_new ();
238   GtkWidget *label = 0;
239   GtkWidget *ok = 0;
240   int i = 0;
241
242   while (parent->parent)
243     parent = parent->parent;
244
245   head = msg;
246   while (head)
247     {
248       char name[20];
249       char *s = strchr (head, '\n');
250       if (s) *s = 0;
251
252       sprintf (name, "label%d", i++);
253
254       {
255         char buf[255];
256         label = gtk_label_new (head);
257         sprintf (buf, "warning_dialog.%s.font", name);
258         GTK_WIDGET (label)->style = gtk_style_copy (GTK_WIDGET (label)->style);
259         GTK_WIDGET (label)->style->font =
260           gdk_font_load (get_string_resource (buf, "Dialog.Label.Font"));
261         if (center <= 0)
262           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
263         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
264                             label, TRUE, TRUE, 0);
265         gtk_widget_show (label);
266       }
267
268       if (s)
269         head = s+1;
270       else
271         head = 0;
272
273       center--;
274     }
275
276   label = gtk_label_new ("");
277   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
278                       label, TRUE, TRUE, 0);
279   gtk_widget_show (label);
280
281   label = gtk_hbutton_box_new ();
282   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
283                       label, TRUE, TRUE, 0);
284
285   ok = gtk_button_new_with_label ("OK");
286   gtk_container_add (GTK_CONTAINER (label), ok);
287
288   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
289   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
290   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
291   gtk_widget_show (ok);
292   gtk_widget_show (label);
293   gtk_widget_show (dialog);
294 /*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
295
296   gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
297                              GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
298                              (gpointer) dialog);
299   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
300                                 GTK_WIDGET (parent)->window);
301
302   gdk_window_show (GTK_WIDGET (dialog)->window);
303   gdk_window_raise (GTK_WIDGET (dialog)->window);
304
305   free (msg);
306 }
307
308
309 static void
310 run_cmd (GtkWidget *widget, Atom command, int arg)
311 {
312   char *err = 0;
313   int status;
314
315   apply_changes_and_save (widget);
316   status = xscreensaver_command (gdk_display, command, arg, False, &err);
317   if (status < 0)
318     {
319       char buf [255];
320       if (err)
321         sprintf (buf, "Error:\n\n%s", err);
322       else
323         strcpy (buf, "Unknown error!");
324       warning_dialog (widget, buf, 100);
325     }
326   if (err) free (err);
327 }
328
329
330 static void
331 run_hack (GtkWidget *widget, int which, Bool report_errors_p)
332 {
333   if (which < 0) return;
334   apply_changes_and_save (widget);
335   if (report_errors_p)
336     run_cmd (widget, XA_DEMO, which + 1);
337   else
338     {
339       char *s = 0;
340       xscreensaver_command (gdk_display, XA_DEMO, which + 1, False, &s);
341       if (s) free (s);
342     }
343 }
344
345
346 \f
347 /* Button callbacks
348  */
349
350 void
351 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
352 {
353   apply_changes_and_save (GTK_WIDGET (menuitem));
354   gtk_main_quit ();
355 }
356
357 static void
358 wm_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
359 {
360   apply_changes_and_save (widget);
361   gtk_main_quit ();
362 }
363
364
365 void
366 cut_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
367 {
368   /* #### */
369   warning_dialog (GTK_WIDGET (menuitem),
370                   "Error:\n\n"
371                   "cut unimplemented\n", 1);
372 }
373
374
375 void
376 copy_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
377 {
378   /* #### */
379   warning_dialog (GTK_WIDGET (menuitem),
380                   "Error:\n\n"
381                   "copy unimplemented\n", 1);
382 }
383
384
385 void
386 paste_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
387 {
388   /* #### */
389   warning_dialog (GTK_WIDGET (menuitem),
390                   "Error:\n\n"
391                   "paste unimplemented\n", 1);
392 }
393
394
395 void
396 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
397 {
398   char buf [2048];
399   char *s = strdup (screensaver_id + 4);
400   char *s2;
401
402   s2 = strchr (s, ',');
403   *s2 = 0;
404   s2 += 2;
405
406   sprintf (buf, "%s\n%s\n\n"
407            "For updates, check http://www.jwz.org/xscreensaver/",
408            s, s2);
409   free (s);
410
411   warning_dialog (GTK_WIDGET (menuitem), buf, 100);
412 }
413
414
415 void
416 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
417 {
418   /* prefs_pair *pair = (prefs_pair *) client_data; */
419   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
420
421   saver_preferences *p =  pair->a;
422   char *help_command;
423
424   if (!p->help_url || !*p->help_url)
425     {
426       warning_dialog (GTK_WIDGET (menuitem),
427                       "Error:\n\n"
428                       "No Help URL has been specified.\n", 100);
429       return;
430     }
431
432   help_command = (char *) malloc (strlen (p->load_url_command) +
433                                   (strlen (p->help_url) * 2) + 20);
434   strcpy (help_command, "( ");
435   sprintf (help_command + strlen(help_command),
436            p->load_url_command, p->help_url, p->help_url);
437   strcat (help_command, " ) &");
438   system (help_command);
439   free (help_command);
440 }
441
442
443 void
444 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
445 {
446   run_cmd (GTK_WIDGET (menuitem), XA_ACTIVATE, 0);
447 }
448
449
450 void
451 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
452 {
453   run_cmd (GTK_WIDGET (menuitem), XA_LOCK, 0);
454 }
455
456
457 void
458 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
459 {
460   run_cmd (GTK_WIDGET (menuitem), XA_EXIT, 0);
461 }
462
463
464 void
465 restart_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
466 {
467 #if 0
468   run_cmd (GTK_WIDGET (menuitem), XA_RESTART, 0);
469 #else
470   apply_changes_and_save (GTK_WIDGET (menuitem));
471   xscreensaver_command (gdk_display, XA_EXIT, 0, False, NULL);
472   sleep (1);
473   system ("xscreensaver -nosplash &");
474 #endif
475 }
476
477
478 static int _selected_hack_number = -1;
479
480 static int
481 selected_hack_number (GtkWidget *toplevel)
482 {
483 #if 0
484   GtkViewport *vp = GTK_VIEWPORT (name_to_widget (toplevel, "viewport"));
485   GtkList *list_widget = GTK_LIST (GTK_BIN(vp)->child);
486   GList *slist = list_widget->selection;
487   GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0);
488   int which = (selected
489                ? gtk_list_child_position (list_widget, GTK_WIDGET (selected))
490                : -1);
491   return which;
492 #else
493   return _selected_hack_number;
494 #endif
495 }
496
497
498 static int
499 demo_write_init_file (GtkWidget *widget, saver_preferences *p)
500 {
501   if (!write_init_file (p, short_version, False))
502     return 0;
503   else
504     {
505       const char *f = init_file_name();
506       if (!f || !*f)
507         warning_dialog (widget,
508                         "Error:\n\nCouldn't determine init file name!\n",
509                         100);
510       else
511         {
512           char *b = (char *) malloc (strlen(f) + 1024);
513           sprintf (b, "Error:\n\nCouldn't write %s\n", f);
514           warning_dialog (widget, b, 100);
515           free (b);
516         }
517       return -1;
518     }
519 }
520
521
522 static int
523 apply_changes_and_save (GtkWidget *widget)
524 {
525   /* prefs_pair *pair = (prefs_pair *) client_data; */
526   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
527   saver_preferences *p =  pair->a;
528   GtkList *list_widget =
529     GTK_LIST (name_to_widget (widget, "list"));
530   int which = selected_hack_number (widget);
531
532   GtkEntry *cmd = GTK_ENTRY (name_to_widget (widget, "cmd_text"));
533   GtkToggleButton *enabled =
534     GTK_TOGGLE_BUTTON (name_to_widget (widget, "enabled"));
535   GtkCombo *vis = GTK_COMBO (name_to_widget (widget, "visual_combo"));
536
537   Bool enabled_p = gtk_toggle_button_get_active (enabled);
538   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
539   const char *command = gtk_entry_get_text (cmd);
540   
541   char c;
542   unsigned long id;
543
544   if (which < 0) return -1;
545
546   if (maybe_reload_init_file (widget, pair) != 0)
547     return 1;
548
549   /* Sanity-check and canonicalize whatever the user typed into the combo box.
550    */
551   if      (!strcasecmp (visual, ""))                   visual = "";
552   else if (!strcasecmp (visual, "any"))                visual = "";
553   else if (!strcasecmp (visual, "default"))            visual = "Default";
554   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
555   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
556   else if (!strcasecmp (visual, "best"))               visual = "Best";
557   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
558   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
559   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
560   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
561   else if (!strcasecmp (visual, "color"))              visual = "Color";
562   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
563   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
564   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
565   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
566   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
567   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
568   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
569   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
570   else if (1 == sscanf (visual, " %ld %c", &id, &c))   ;
571   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
572   else
573     {
574       gdk_beep ();                                /* unparsable */
575       visual = "";
576       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");
577     }
578
579   ensure_selected_item_visible (GTK_WIDGET (list_widget));
580
581   if (!p->screenhacks[which]->visual)
582     p->screenhacks[which]->visual = strdup ("");
583   if (!p->screenhacks[which]->command)
584     p->screenhacks[which]->command = strdup ("");
585
586   if (p->screenhacks[which]->enabled_p != enabled_p ||
587       !!strcasecmp (p->screenhacks[which]->visual, visual) ||
588       !!strcasecmp (p->screenhacks[which]->command, command))
589     {
590       /* Something was changed -- store results into the struct,
591          and write the file.
592        */
593       free (p->screenhacks[which]->visual);
594       free (p->screenhacks[which]->command);
595       p->screenhacks[which]->visual = strdup (visual);
596       p->screenhacks[which]->command = strdup (command);
597       p->screenhacks[which]->enabled_p = enabled_p;
598
599       return demo_write_init_file (widget, p);
600     }
601
602   /* No changes made */
603   return 0;
604 }
605
606 void
607 run_this_cb (GtkButton *button, gpointer user_data)
608 {
609   int which = selected_hack_number (GTK_WIDGET (button));
610   if (which < 0) return;
611   if (0 == apply_changes_and_save (GTK_WIDGET (button)))
612     run_hack (GTK_WIDGET (button), which, True);
613 }
614
615
616 void
617 manual_cb (GtkButton *button, gpointer user_data)
618 {
619   /* prefs_pair *pair = (prefs_pair *) client_data; */
620   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
621   saver_preferences *p =  pair->a;
622   GtkList *list_widget =
623     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
624   int which = selected_hack_number (GTK_WIDGET (button));
625   char *name, *name2, *cmd, *s;
626   if (which < 0) return;
627   apply_changes_and_save (GTK_WIDGET (button));
628   ensure_selected_item_visible (GTK_WIDGET (list_widget));
629
630   name = strdup (p->screenhacks[which]->command);
631   name2 = name;
632   while (isspace (*name2)) name2++;
633   s = name2;
634   while (*s && !isspace (*s)) s++;
635   *s = 0;
636   s = strrchr (name2, '/');
637   if (s) name = s+1;
638
639   cmd = get_string_resource ("manualCommand", "ManualCommand");
640   if (cmd)
641     {
642       char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100);
643       strcpy (cmd2, "( ");
644       sprintf (cmd2 + strlen (cmd2),
645                cmd,
646                name2, name2, name2, name2);
647       strcat (cmd2, " ) &");
648       system (cmd2);
649       free (cmd2);
650     }
651   else
652     {
653       warning_dialog (GTK_WIDGET (button),
654                       "Error:\n\nno `manualCommand' resource set.",
655                       100);
656     }
657
658   free (name);
659 }
660
661
662 void
663 run_next_cb (GtkButton *button, gpointer user_data)
664 {
665   /* prefs_pair *pair = (prefs_pair *) client_data; */
666   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
667   saver_preferences *p =  pair->a;
668
669   GtkList *list_widget =
670     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
671   int which = selected_hack_number (GTK_WIDGET (button));
672
673   if (which < 0)
674     which = 0;
675   else
676     which++;
677
678   if (which >= p->screenhacks_count)
679     which = 0;
680
681   apply_changes_and_save (GTK_WIDGET (button));
682   gtk_list_select_item (GTK_LIST (list_widget), which);
683   ensure_selected_item_visible (GTK_WIDGET (list_widget));
684   populate_demo_window (GTK_WIDGET (button), which, pair);
685   run_hack (GTK_WIDGET (button), which, False);
686 }
687
688
689 void
690 run_prev_cb (GtkButton *button, gpointer user_data)
691 {
692   /* prefs_pair *pair = (prefs_pair *) client_data; */
693   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
694   saver_preferences *p =  pair->a;
695
696   GtkList *list_widget =
697     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
698   int which = selected_hack_number (GTK_WIDGET (button));
699
700   if (which < 0)
701     which = p->screenhacks_count - 1;
702   else
703     which--;
704
705   if (which < 0)
706     which = p->screenhacks_count - 1;
707
708   apply_changes_and_save (GTK_WIDGET (button));
709   gtk_list_select_item (GTK_LIST (list_widget), which);
710   ensure_selected_item_visible (GTK_WIDGET (list_widget));
711   populate_demo_window (GTK_WIDGET (button), which, pair);
712   run_hack (GTK_WIDGET (button), which, False);
713 }
714
715
716 /* Helper for the text fields that contain time specifications:
717    this parses the text, and does error checking.
718  */
719 static void 
720 hack_time_text (GtkWidget *widget, const char *line, Time *store, Bool sec_p)
721 {
722   if (*line)
723     {
724       int value;
725       value = parse_time ((char *) line, sec_p, True);
726       value *= 1000;    /* Time measures in microseconds */
727       if (value < 0)
728         {
729           char b[255];
730           sprintf (b,
731                    "Error:\n\n"
732                    "Unparsable time format: \"%s\"\n",
733                    line);
734           warning_dialog (widget, b, 100);
735         }
736       else
737         *store = value;
738     }
739 }
740
741
742 void
743 prefs_ok_cb (GtkButton *button, gpointer user_data)
744 {
745   /* prefs_pair *pair = (prefs_pair *) client_data; */
746   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
747
748   saver_preferences *p =  pair->a;
749   saver_preferences *p2 = pair->b;
750   Bool changed = False;
751
752 # define SECONDS(field, name) \
753   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
754                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
755                   (field), \
756                   True)
757
758 # define MINUTES(field, name) \
759   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
760                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
761                   (field), \
762                   False)
763
764 # define INTEGER(field, name) do { \
765     char *line = gtk_entry_get_text (\
766                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))); \
767     unsigned int value; \
768     char c; \
769     if (! *line) \
770       ; \
771     else if (sscanf (line, "%u%c", &value, &c) != 1) \
772       { \
773         char b[255]; \
774         sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", line); \
775         warning_dialog (GTK_WIDGET (button), b, 100); \
776       } \
777    else \
778      *(field) = value; \
779   } while(0)
780
781 # define CHECKBOX(field, name) \
782   field = gtk_toggle_button_get_active (\
783              GTK_TOGGLE_BUTTON (name_to_widget (GTK_WIDGET(button), (name))))
784
785   MINUTES (&p2->timeout,        "timeout_text");
786   MINUTES (&p2->cycle,          "cycle_text");
787   SECONDS (&p2->fade_seconds,   "fade_text");
788   INTEGER (&p2->fade_ticks,     "ticks_text");
789   MINUTES (&p2->lock_timeout,   "lock_text");
790   SECONDS (&p2->passwd_timeout, "pass_text");
791   CHECKBOX (p2->verbose_p,      "verbose_button");
792   CHECKBOX (p2->install_cmap_p, "install_button");
793   CHECKBOX (p2->fade_p,         "fade_button");
794   CHECKBOX (p2->unfade_p,       "unfade_button");
795   CHECKBOX (p2->lock_p,         "lock_button");
796
797 # undef SECONDS
798 # undef MINUTES
799 # undef INTEGER
800 # undef CHECKBOX
801
802 # define COPY(field) \
803   if (p->field != p2->field) changed = True; \
804   p->field = p2->field
805
806   COPY(timeout);
807   COPY(cycle);
808   COPY(lock_timeout);
809   COPY(passwd_timeout);
810   COPY(fade_seconds);
811   COPY(fade_ticks);
812   COPY(verbose_p);
813   COPY(install_cmap_p);
814   COPY(fade_p);
815   COPY(unfade_p);
816   COPY(lock_p);
817 # undef COPY
818
819   populate_prefs_page (GTK_WIDGET (button), pair);
820
821   if (changed)
822     demo_write_init_file (GTK_WIDGET (button), p);
823 }
824
825
826 void
827 prefs_cancel_cb (GtkButton *button, gpointer user_data)
828 {
829   /* prefs_pair *pair = (prefs_pair *) client_data; */
830   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
831
832   *pair->b = *pair->a;
833   populate_prefs_page (GTK_WIDGET (button), pair);
834 }
835
836
837 static gint
838 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
839                      gpointer client_data)
840 {
841   if (event->type == GDK_2BUTTON_PRESS)
842     {
843       GtkList *list = GTK_LIST (name_to_widget (button, "list"));
844       int which = gtk_list_child_position (list, GTK_WIDGET (button));
845
846       if (which >= 0)
847         run_hack (GTK_WIDGET (button), which, True);
848     }
849
850   return FALSE;
851 }
852
853
854 static void
855 list_select_cb (GtkList *list, GtkWidget *child)
856 {
857   /* prefs_pair *pair = (prefs_pair *) client_data; */
858   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
859
860   int which = gtk_list_child_position (list, GTK_WIDGET (child));
861   apply_changes_and_save (GTK_WIDGET (list));
862   populate_demo_window (GTK_WIDGET (list), which, pair);
863 }
864
865 static void
866 list_unselect_cb (GtkList *list, GtkWidget *child)
867 {
868   /* prefs_pair *pair = (prefs_pair *) client_data; */
869   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
870
871   apply_changes_and_save (GTK_WIDGET (list));
872   populate_demo_window (GTK_WIDGET (list), -1, pair);
873 }
874
875 \f
876 /* Populating the various widgets
877  */
878
879
880 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
881  */
882 static void
883 format_time (char *buf, Time time)
884 {
885   int s = time / 1000;
886   unsigned int h = 0, m = 0;
887   if (s >= 60)
888     {
889       m += (s / 60);
890       s %= 60;
891     }
892   if (m >= 60)
893     {
894       h += (m / 60);
895       m %= 60;
896     }
897   sprintf (buf, "%u:%02u:%02u", h, m, s);
898 }
899
900
901 static char *
902 make_pretty_name (const char *shell_command)
903 {
904   char *s = strdup (shell_command);
905   char *s2;
906   char res_name[255];
907
908   for (s2 = s; *s2; s2++)       /* truncate at first whitespace */
909     if (isspace (*s2))
910       {
911         *s2 = 0;
912         break;
913       }
914
915   s2 = strrchr (s, '/');        /* if pathname, take last component */
916   if (s2)
917     {
918       s2 = strdup (s2+1);
919       free (s);
920       s = s2;
921     }
922
923   if (strlen (s) > 50)          /* 51 is hereby defined as "unreasonable" */
924     s[50] = 0;
925
926   sprintf (res_name, "hacks.%s.name", s);               /* resource? */
927   s2 = get_string_resource (res_name, res_name);
928   if (s2)
929     return s2;
930
931   for (s2 = s; *s2; s2++)       /* if it has any capitals, return it */
932     if (*s2 >= 'A' && *s2 <= 'Z')
933       return s;
934
935   if (s[0] >= 'a' && s[0] <= 'z')                       /* else cap it */
936     s[0] -= 'a'-'A';
937   if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z')        /* (magic leading X) */
938     s[1] -= 'a'-'A';
939   return s;
940 }
941
942
943 /* Finds the number of the last hack to run, and makes that item be
944    selected by default.
945  */
946 static void
947 scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair)
948 {
949   saver_preferences *p =  pair->a;
950   Atom type;
951   int format;
952   unsigned long nitems, bytesafter;
953   CARD32 *data = 0;
954   Display *dpy = gdk_display;
955   int which = 0;
956   GtkList *list;
957
958   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
959                           XA_SCREENSAVER_STATUS,
960                           0, 3, False, XA_INTEGER,
961                           &type, &format, &nitems, &bytesafter,
962                           (unsigned char **) &data)
963       == Success
964       && type == XA_INTEGER
965       && nitems >= 3
966       && data)
967     which = (int) data[2] - 1;
968
969   if (data) free (data);
970
971   if (which < 0)
972     return;
973
974   list = GTK_LIST (name_to_widget (toplevel, "list"));
975   apply_changes_and_save (toplevel);
976   if (which < p->screenhacks_count)
977     {
978       gtk_list_select_item (list, which);
979       ensure_selected_item_visible (GTK_WIDGET (list));
980       populate_demo_window (toplevel, which, pair);
981     }
982 }
983
984
985
986 static void
987 populate_hack_list (GtkWidget *toplevel, prefs_pair *pair)
988 {
989   saver_preferences *p =  pair->a;
990   GtkList *list = GTK_LIST (name_to_widget (toplevel, "list"));
991   screenhack **hacks = p->screenhacks;
992   screenhack **h;
993
994   for (h = hacks; h && *h; h++)
995     {
996       GtkWidget *line;
997       char *pretty_name = (h[0]->name
998                            ? strdup (h[0]->name)
999                            : make_pretty_name (h[0]->command));
1000
1001       line = gtk_list_item_new_with_label (pretty_name);
1002       free (pretty_name);
1003
1004       gtk_container_add (GTK_CONTAINER (list), line);
1005       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
1006                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
1007                           (gpointer) pair);
1008 #if 0 /* #### */
1009       GTK_WIDGET (GTK_BIN(line)->child)->style =
1010         gtk_style_copy (GTK_WIDGET (text_line)->style);
1011 #endif
1012       gtk_widget_show (line);
1013     }
1014
1015   gtk_signal_connect (GTK_OBJECT (list), "select_child",
1016                       GTK_SIGNAL_FUNC (list_select_cb),
1017                       (gpointer) pair);
1018   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
1019                       GTK_SIGNAL_FUNC (list_unselect_cb),
1020                       (gpointer) pair);
1021 }
1022
1023
1024 static void
1025 populate_prefs_page (GtkWidget *top, prefs_pair *pair)
1026 {
1027   saver_preferences *p =  pair->a;
1028   char s[100];
1029
1030   format_time (s, p->timeout);
1031   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "timeout_text")), s);
1032   format_time (s, p->cycle);
1033   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "cycle_text")), s);
1034   format_time (s, p->lock_timeout);
1035   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "lock_text")), s);
1036   format_time (s, p->passwd_timeout);
1037   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "pass_text")), s);
1038   format_time (s, p->fade_seconds);
1039   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "fade_text")), s);
1040   sprintf (s, "%u", p->fade_ticks);
1041   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "ticks_text")), s);
1042
1043   gtk_toggle_button_set_active (
1044                    GTK_TOGGLE_BUTTON (name_to_widget (top, "verbose_button")),
1045                    p->verbose_p);
1046   gtk_toggle_button_set_active (
1047                    GTK_TOGGLE_BUTTON (name_to_widget (top, "install_button")),
1048                    p->install_cmap_p);
1049   gtk_toggle_button_set_active (
1050                    GTK_TOGGLE_BUTTON (name_to_widget (top, "fade_button")),
1051                    p->fade_p);
1052   gtk_toggle_button_set_active (
1053                    GTK_TOGGLE_BUTTON (name_to_widget (top, "unfade_button")),
1054                    p->unfade_p);
1055   gtk_toggle_button_set_active (
1056                    GTK_TOGGLE_BUTTON (name_to_widget (top, "lock_button")),
1057                    p->lock_p);
1058
1059
1060   {
1061     Bool found_any_writable_cells = False;
1062     Display *dpy = gdk_display;
1063     int nscreens = ScreenCount(dpy);
1064     int i;
1065     for (i = 0; i < nscreens; i++)
1066       {
1067         Screen *s = ScreenOfDisplay (dpy, i);
1068         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1069           {
1070             found_any_writable_cells = True;
1071             break;
1072           }
1073       }
1074
1075     gtk_widget_set_sensitive (
1076                            GTK_WIDGET (name_to_widget (top, "fade_label")),
1077                            found_any_writable_cells);
1078     gtk_widget_set_sensitive (
1079                            GTK_WIDGET (name_to_widget (top, "ticks_label")),
1080                            found_any_writable_cells);
1081     gtk_widget_set_sensitive (
1082                            GTK_WIDGET (name_to_widget (top, "fade_text")),
1083                            found_any_writable_cells);
1084     gtk_widget_set_sensitive (
1085                            GTK_WIDGET (name_to_widget (top, "ticks_text")),
1086                            found_any_writable_cells);
1087     gtk_widget_set_sensitive (
1088                            GTK_WIDGET (name_to_widget (top, "install_button")),
1089                            found_any_writable_cells);
1090     gtk_widget_set_sensitive (
1091                            GTK_WIDGET (name_to_widget (top, "fade_button")),
1092                            found_any_writable_cells);
1093     gtk_widget_set_sensitive (
1094                            GTK_WIDGET (name_to_widget (top, "unfade_button")),
1095                            found_any_writable_cells);
1096   }
1097
1098 }
1099
1100
1101 static void
1102 sensitize_demo_widgets (GtkWidget *toplevel, Bool sensitive_p)
1103 {
1104   const char *names[] = { "cmd_label", "cmd_text", "enabled",
1105                           "visual", "visual_combo",
1106                           "demo", "manual" };
1107   int i;
1108   for (i = 0; i < countof(names); i++)
1109     {
1110       GtkWidget *w = name_to_widget (toplevel, names[i]);
1111       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
1112     }
1113
1114   /* I don't know how to handle these yet... */
1115   {
1116     const char *names2[] = { "cut_menu", "copy_menu", "paste_menu" };
1117     for (i = 0; i < countof(names2); i++)
1118       {
1119         GtkWidget *w = name_to_widget (toplevel, names2[i]);
1120         gtk_widget_set_sensitive (GTK_WIDGET(w), False);
1121       }
1122   }
1123 }
1124
1125
1126 /* Even though we've given these text fields a maximum number of characters,
1127    their default size is still about 30 characters wide -- so measure out
1128    a string in their font, and resize them to just fit that.
1129  */
1130 static void
1131 fix_text_entry_sizes (GtkWidget *toplevel)
1132 {
1133   const char *names[] = { "timeout_text", "cycle_text", "fade_text",
1134                           "ticks_text", "lock_text", "pass_text" };
1135   int i;
1136   int width = 0;
1137   GtkWidget *w;
1138
1139   for (i = 0; i < countof(names); i++)
1140     {
1141       w = GTK_WIDGET (name_to_widget (toplevel, names[i]));
1142       if (width == 0)
1143         width = gdk_text_width (w->style->font, "00:00:00_", 9);
1144       gtk_widget_set_usize (w, width, -2);
1145     }
1146
1147   /* Now fix the size of the combo box.
1148    */
1149   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "visual_combo"));
1150   w = GTK_COMBO (w)->entry;
1151   width = gdk_text_width (w->style->font, "PseudoColor___", 14);
1152   gtk_widget_set_usize (w, width, -2);
1153
1154 #if 0
1155   /* Now fix the size of the list.
1156    */
1157   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "list"));
1158   width = gdk_text_width (w->style->font, "nnnnnnnnnnnnnnnnnnnnnn", 22);
1159   gtk_widget_set_usize (w, width, -2);
1160 #endif
1161 }
1162
1163
1164
1165 \f
1166 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
1167  */
1168
1169 static char *up_arrow_xpm[] = {
1170   "15 15 4 1",
1171   "     c None",
1172   "-    c #FFFFFF",
1173   "+    c #D6D6D6",
1174   "@    c #000000",
1175
1176   "       @       ",
1177   "       @       ",
1178   "      -+@      ",
1179   "      -+@      ",
1180   "     -+++@     ",
1181   "     -+++@     ",
1182   "    -+++++@    ",
1183   "    -+++++@    ",
1184   "   -+++++++@   ",
1185   "   -+++++++@   ",
1186   "  -+++++++++@  ",
1187   "  -+++++++++@  ",
1188   " -+++++++++++@ ",
1189   " @@@@@@@@@@@@@ ",
1190   "               ",
1191
1192   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1193      the end of the array (Gtk 1.2.5.) */
1194   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1195   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1196 };
1197
1198 static char *down_arrow_xpm[] = {
1199   "15 15 4 1",
1200   "     c None",
1201   "-    c #FFFFFF",
1202   "+    c #D6D6D6",
1203   "@    c #000000",
1204
1205   "               ",
1206   " ------------- ",
1207   " -+++++++++++@ ",
1208   "  -+++++++++@  ",
1209   "  -+++++++++@  ",
1210   "   -+++++++@   ",
1211   "   -+++++++@   ",
1212   "    -+++++@    ",
1213   "    -+++++@    ",
1214   "     -+++@     ",
1215   "     -+++@     ",
1216   "      -+@      ",
1217   "      -+@      ",
1218   "       @       ",
1219   "       @       ",
1220
1221   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1222      the end of the array (Gtk 1.2.5.) */
1223   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1224   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1225 };
1226
1227 static void
1228 pixmapify_buttons (GtkWidget *toplevel)
1229 {
1230   GdkPixmap *pixmap;
1231   GdkBitmap *mask;
1232   GtkWidget *pixmapwid;
1233   GtkStyle *style;
1234   GtkWidget *w;
1235
1236   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "next"));
1237   style = gtk_widget_get_style (w);
1238   mask = 0;
1239   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
1240                                          &style->bg[GTK_STATE_NORMAL],
1241                                          (gchar **) down_arrow_xpm);
1242   pixmapwid = gtk_pixmap_new (pixmap, mask);
1243   gtk_widget_show (pixmapwid);
1244   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
1245   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
1246
1247   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "prev"));
1248   style = gtk_widget_get_style (w);
1249   mask = 0;
1250   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
1251                                          &style->bg[GTK_STATE_NORMAL],
1252                                          (gchar **) up_arrow_xpm);
1253   pixmapwid = gtk_pixmap_new (pixmap, mask);
1254   gtk_widget_show (pixmapwid);
1255   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
1256   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
1257 }
1258
1259
1260 \f
1261 /* Work around a Gtk bug that causes label widgets to wrap text too early.
1262  */
1263
1264 static void
1265 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
1266                                              GtkAllocation *allocation,
1267                                              void *foo)
1268 {
1269   GtkRequisition req;
1270   GtkWidgetAuxInfo *aux_info;
1271
1272   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
1273
1274   aux_info->width = allocation->width;
1275   aux_info->height = -2;
1276   aux_info->x = -1;
1277   aux_info->y = -1;
1278
1279   gtk_widget_size_request (label, &req);
1280 }
1281
1282
1283 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
1284  */
1285 static void
1286 eschew_gtk_lossage (GtkWidget *toplevel)
1287 {
1288   GtkWidgetAuxInfo *aux_info;
1289   GtkWidget *label = GTK_WIDGET (name_to_widget (toplevel, "doc"));
1290
1291   aux_info = g_new0 (GtkWidgetAuxInfo, 1);
1292   aux_info->width = label->allocation.width;
1293   aux_info->height = -2;
1294   aux_info->x = -1;
1295   aux_info->y = -1;
1296
1297   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
1298
1299   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
1300                       you_are_not_a_unique_or_beautiful_snowflake, NULL);
1301
1302   gtk_widget_queue_resize (label); 
1303 }
1304
1305
1306 char *
1307 get_hack_blurb (screenhack *hack)
1308 {
1309   char *doc_string;
1310   char *prog_name = strdup (hack->command);
1311   char *pretty_name = (hack->name
1312                        ? strdup (hack->name)
1313                        : make_pretty_name (hack->command));
1314   char doc_name[255], doc_class[255];
1315   char *s, *s2;
1316
1317   for (s = prog_name; *s && !isspace(*s); s++)
1318     ;
1319   *s = 0;
1320   s = strrchr (prog_name, '/');
1321   if (s) strcpy (prog_name, s+1);
1322
1323   sprintf (doc_name,  "hacks.%s.documentation", pretty_name);
1324   sprintf (doc_class, "hacks.%s.documentation", prog_name);
1325   free (prog_name);
1326   free (pretty_name);
1327
1328   doc_string = get_string_resource (doc_name, doc_class);
1329   if (doc_string)
1330     {
1331       for (s = doc_string; *s; s++)
1332         {
1333           if (*s == '\n')
1334             {
1335               /* skip over whitespace at beginning of line */
1336               s++;
1337               while (*s && (*s == ' ' || *s == '\t'))
1338                 s++;
1339             }
1340           else if (*s == ' ' || *s == '\t')
1341             {
1342               /* compress all other horizontal whitespace. */
1343               *s = ' ';
1344               s++;
1345               for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
1346                 ;
1347               if (s2 > s) strcpy (s, s2);
1348               s--;
1349             }
1350         }
1351
1352       while (*s && isspace (*s))      /* Strip trailing whitespace */
1353         *(--s) = 0;
1354
1355       /* Delete whitespace at end of each line. */
1356       for (; s > doc_string; s--)
1357         if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
1358           {
1359             for (s2 = s-1;
1360                  s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
1361                  s2--)
1362               ;
1363             s2++;
1364             if (s2 < s) strcpy (s2, s);
1365             s = s2;
1366           }
1367       
1368       /* Delete leading blank lines. */
1369       for (s = doc_string; *s == '\n'; s++)
1370         ;
1371       if (s > doc_string) strcpy (doc_string, s);
1372     }
1373   else
1374     {
1375       static int doc_installed = 0;
1376       if (doc_installed == 0)
1377         {
1378           if (get_boolean_resource ("hacks.documentation.isInstalled",
1379                                     "hacks.documentation.isInstalled"))
1380             doc_installed = 1;
1381           else
1382             doc_installed = -1;
1383         }
1384
1385       if (doc_installed < 0)
1386         doc_string =
1387           strdup ("Error:\n\n"
1388                   "The documentation strings do not appear to be "
1389                   "installed.  This is probably because there is "
1390                   "an \"XScreenSaver\" app-defaults file installed "
1391                   "that is from an older version of the program. "
1392                   "To fix this problem, delete that file, or "
1393                   "install a current version (either will work.)");
1394       else
1395         doc_string = strdup ("");
1396     }
1397
1398   return doc_string;
1399 }
1400
1401
1402 static void
1403 populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
1404 {
1405   saver_preferences *p = pair->a;
1406   screenhack *hack = (which >= 0 && which < p->screenhacks_count
1407                       ? p->screenhacks[which] : 0);
1408   GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame"));
1409   GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc"));
1410   GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text"));
1411   GtkToggleButton *enabled =
1412     GTK_TOGGLE_BUTTON (name_to_widget (toplevel, "enabled"));
1413   GtkCombo *vis = GTK_COMBO (name_to_widget (toplevel, "visual_combo"));
1414
1415   char *pretty_name = (hack
1416                        ? (hack->name
1417                           ? strdup (hack->name)
1418                           : make_pretty_name (hack->command))
1419                        : 0);
1420   char *doc_string = hack ? get_hack_blurb (hack) : 0;
1421
1422   gtk_frame_set_label (frame, (pretty_name ? pretty_name : ""));
1423   gtk_label_set_text (doc, (doc_string ? doc_string : ""));
1424   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
1425   gtk_entry_set_position (cmd, 0);
1426   gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False));
1427   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
1428                       (hack
1429                        ? (hack->visual && *hack->visual
1430                           ? hack->visual
1431                           : "Any")
1432                        : ""));
1433
1434   gtk_container_resize_children (GTK_CONTAINER (GTK_WIDGET (doc)->parent));
1435
1436   sensitize_demo_widgets (toplevel, (hack ? True : False));
1437
1438   if (pretty_name) free (pretty_name);
1439   if (doc_string) free (doc_string);
1440
1441   _selected_hack_number = which;
1442 }
1443
1444
1445 static void
1446 widget_deleter (GtkWidget *widget, gpointer data)
1447 {
1448   /* #### Well, I want to destroy these widgets, but if I do that, they get
1449      referenced again, and eventually I get a SEGV.  So instead of
1450      destroying them, I'll just hide them, and leak a bunch of memory
1451      every time the disk file changes.  Go go go Gtk!
1452
1453      #### Ok, that's a lie, I get a crash even if I just hide the widget
1454      and don't ever delete it.  Fuck!
1455    */
1456 #if 0
1457   gtk_widget_destroy (widget);
1458 #else
1459   gtk_widget_hide (widget);
1460 #endif
1461 }
1462
1463
1464 static int
1465 maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair)
1466 {
1467   int status = 0;
1468   saver_preferences *p =  pair->a;
1469
1470   static Bool reentrant_lock = False;
1471   if (reentrant_lock) return 0;
1472   reentrant_lock = True;
1473
1474   if (init_file_changed_p (p))
1475     {
1476       const char *f = init_file_name();
1477       char *b;
1478       int which;
1479       GtkList *list;
1480
1481       if (!f || !*f) return 0;
1482       b = (char *) malloc (strlen(f) + 1024);
1483       sprintf (b,
1484                "Warning:\n\n"
1485                "file \"%s\" has changed, reloading.\n",
1486                f);
1487       warning_dialog (widget, b, 100);
1488       free (b);
1489
1490       load_init_file (p);
1491
1492       which = selected_hack_number (widget);
1493       list = GTK_LIST (name_to_widget (widget, "list"));
1494       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
1495       populate_hack_list (widget, pair);
1496       gtk_list_select_item (list, which);
1497       populate_prefs_page (widget, pair);
1498       populate_demo_window (widget, which, pair);
1499       ensure_selected_item_visible (GTK_WIDGET (list));
1500
1501       status = 1;
1502     }
1503
1504   reentrant_lock = False;
1505   return status;
1506 }
1507
1508
1509 \f
1510 /* The main demo-mode command loop.
1511  */
1512
1513 #if 0
1514 static Bool
1515 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1516         XrmRepresentation *type, XrmValue *value, XPointer closure)
1517 {
1518   int i;
1519   for (i = 0; quarks[i]; i++)
1520     {
1521       if (bindings[i] == XrmBindTightly)
1522         fprintf (stderr, (i == 0 ? "" : "."));
1523       else if (bindings[i] == XrmBindLoosely)
1524         fprintf (stderr, "*");
1525       else
1526         fprintf (stderr, " ??? ");
1527       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1528     }
1529
1530   fprintf (stderr, ": %s\n", (char *) value->addr);
1531
1532   return False;
1533 }
1534 #endif
1535
1536
1537 static void
1538 the_network_is_not_the_computer (GtkWidget *parent)
1539 {
1540   Display *dpy = gdk_display;
1541   char *rversion, *ruser, *rhost;
1542   char *luser, *lhost;
1543   char *msg = 0;
1544   struct passwd *p = getpwuid (getuid ());
1545   const char *d = DisplayString (dpy);
1546
1547 # if defined(HAVE_UNAME)
1548   struct utsname uts;
1549   if (uname (&uts) < 0)
1550     lhost = "<UNKNOWN>";
1551   else
1552     lhost = uts.nodename;
1553 # elif defined(VMS)
1554   strcpy (lhost, getenv("SYS$NODE"));
1555 # else  /* !HAVE_UNAME && !VMS */
1556   strcat (lhost, "<UNKNOWN>");
1557 # endif /* !HAVE_UNAME && !VMS */
1558
1559   if (p && p->pw_name)
1560     luser = p->pw_name;
1561   else
1562     luser = "???";
1563
1564   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1565
1566   /* Make a buffer that's big enough for a number of copies of all the
1567      strings, plus some. */
1568   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1569                                (ruser ? strlen(ruser) : 0) +
1570                                (rhost ? strlen(rhost) : 0) +
1571                                strlen(lhost) +
1572                                strlen(luser) +
1573                                strlen(d) +
1574                                1024));
1575   *msg = 0;
1576
1577   if (!rversion || !*rversion)
1578     {
1579       sprintf (msg,
1580                "Warning:\n\n"
1581                "The XScreenSaver daemon doesn't seem to be running\n"
1582                "on display \"%s\".  You can launch it by selecting\n"
1583                "`Restart Daemon' from the File menu, or by typing\n"
1584                "\"xscreensaver &\" in a shell.",
1585                d);
1586     }
1587   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1588     {
1589       /* Warn that the two processes are running as different users.
1590        */
1591       sprintf(msg,
1592                "Warning:\n\n"
1593               "%s is running as user \"%s\" on host \"%s\".\n"
1594               "But the xscreensaver managing display \"%s\"\n"
1595               "is running as user \"%s\" on host \"%s\".\n"
1596               "\n"
1597               "Since they are different users, they won't be reading/writing\n"
1598               "the same ~/.xscreensaver file, so %s isn't\n"
1599               "going to work right.\n"
1600               "\n"
1601               "Either re-run %s as \"%s\", or re-run\n"
1602               "xscreensaver as \"%s\" (which you can do by\n"
1603               "selecting `Restart Daemon' from the File menu.)\n",
1604               progname, luser, lhost,
1605               d,
1606               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1607               progname,
1608               progname, (ruser ? ruser : "???"),
1609               luser);
1610     }
1611   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1612     {
1613       /* Warn that the two processes are running on different hosts.
1614        */
1615       sprintf (msg,
1616                "Warning:\n\n"
1617                "%s is running as user \"%s\" on host \"%s\".\n"
1618                "But the xscreensaver managing display \"%s\"\n"
1619                "is running as user \"%s\" on host \"%s\".\n"
1620                "\n"
1621                "If those two machines don't share a file system (that is,\n"
1622                "if they don't see the same ~%s/.xscreensaver file) then\n"
1623                "%s won't work right.\n"
1624                "\n"
1625                "You can restart the daemon on \"%s\" as \"%s\" by\n"
1626                "selecting `Restart Daemon' from the File menu.)",
1627                progname, luser, lhost,
1628                d,
1629                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1630                luser,
1631                progname,
1632                lhost, luser);
1633     }
1634   else if (!!strcmp (rversion, short_version))
1635     {
1636       /* Warn that the version numbers don't match.
1637        */
1638       sprintf (msg,
1639                "Warning:\n\n"
1640                "This is %s version %s.\n"
1641                "But the xscreensaver managing display \"%s\"\n"
1642                "is version %s.  This could cause problems.",
1643                progname, short_version,
1644                d,
1645                rversion);
1646     }
1647
1648
1649   if (*msg)
1650     warning_dialog (parent, msg, 1);
1651
1652   free (msg);
1653 }
1654
1655
1656 /* We use this error handler so that X errors are preceeded by the name
1657    of the program that generated them.
1658  */
1659 static int
1660 demo_ehandler (Display *dpy, XErrorEvent *error)
1661 {
1662   fprintf (stderr, "\nX error in %s:\n", progname);
1663   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
1664     exit (-1);
1665   else
1666     fprintf (stderr, " (nonfatal.)\n");
1667   return 0;
1668 }
1669
1670
1671 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
1672    of the program that generated them; and also that we can ignore one
1673    particular bogus error message that Gdk madly spews.
1674  */
1675 static void
1676 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
1677                const gchar *message, gpointer user_data)
1678 {
1679   /* Ignore the message "Got event for unknown window: 0x...".
1680      Apparently some events are coming in for the xscreensaver window
1681      (presumably reply events related to the ClientMessage) and Gdk
1682      feels the need to complain about them.  So, just suppress any
1683      messages that look like that one.
1684    */
1685   if (strstr (message, "unknown window"))
1686     return;
1687
1688   fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain,
1689            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
1690             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
1691             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
1692             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
1693             log_level == G_LOG_LEVEL_INFO     ? "info" :
1694             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
1695            message,
1696            ((!*message || message[strlen(message)-1] != '\n')
1697             ? "\n" : ""));
1698 }
1699
1700
1701 static char *defaults[] = {
1702 #include "XScreenSaver_ad.h"
1703  0
1704 };
1705
1706 int
1707 main (int argc, char **argv)
1708 {
1709   XtAppContext app;
1710   prefs_pair Pair, *pair;
1711   saver_preferences P, P2, *p, *p2;
1712   Bool prefs = False;
1713   int i;
1714   Display *dpy;
1715   Widget toplevel_shell;
1716   GtkWidget *gtk_window;
1717   char *real_progname = argv[0];
1718   char *s;
1719
1720   s = strrchr (real_progname, '/');
1721   if (s) real_progname = s+1;
1722
1723   p = &P;
1724   p2 = &P2;
1725   pair = &Pair;
1726   pair->a = p;
1727   pair->b = p2;
1728   memset (p,  0, sizeof (*p));
1729   memset (p2, 0, sizeof (*p2));
1730
1731   global_prefs_pair = pair;  /* I hate C so much... */
1732
1733   progname = real_progname;
1734
1735   short_version = (char *) malloc (5);
1736   memcpy (short_version, screensaver_id + 17, 4);
1737   short_version [4] = 0;
1738
1739
1740   /* Register our error message logger for every ``log domain'' known.
1741      There's no way to do this globally, so I grepped the Gtk/Gdk sources
1742      for all of the domains that seem to be in use.
1743   */
1744   {
1745     const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule",
1746                                      "GThread", "Gnome", "GnomeUI", 0 };
1747     for (i = 0; domains[i]; i++)
1748       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
1749   }
1750
1751   /* This is gross, but Gtk understands --display and not -display...
1752    */
1753   for (i = 1; i < argc; i++)
1754     if (argv[i][0] && argv[i][1] && 
1755         !strncmp(argv[i], "-display", strlen(argv[i])))
1756       argv[i] = "--display";
1757
1758   /* Let Gtk open the X connection, then initialize Xt to use that
1759      same connection.  Doctor Frankenstein would be proud. */   
1760   gtk_init (&argc, &argv);
1761
1762
1763   /* We must read exactly the same resources as xscreensaver.
1764      That means we must have both the same progclass *and* progname,
1765      at least as far as the resource database is concerned.  So,
1766      put "xscreensaver" in argv[0] while initializing Xt.
1767    */
1768   argv[0] = "xscreensaver";
1769   progname = argv[0];
1770
1771
1772   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
1773    */
1774   XtToolkitInitialize ();
1775   app = XtCreateApplicationContext ();
1776   dpy = gdk_display;
1777   XtAppSetFallbackResources (app, defaults);
1778   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
1779   toplevel_shell = XtAppCreateShell (progname, progclass,
1780                                      applicationShellWidgetClass,
1781                                      dpy, 0, 0);
1782
1783   dpy = XtDisplay (toplevel_shell);
1784   db = XtDatabase (dpy);
1785   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
1786   XSetErrorHandler (demo_ehandler);
1787
1788
1789   /* After doing Xt-style command-line processing, complain about any
1790      unrecognized command-line arguments.
1791    */
1792   for (i = 1; i < argc; i++)
1793     {
1794       char *s = argv[i];
1795       if (s[0] == '-' && s[1] == '-')
1796         s++;
1797       if (!strcmp (s, "-prefs"))
1798         prefs = True;
1799       else
1800         {
1801           fprintf (stderr, "%s: unknown option: %s\n", real_progname, argv[i]);
1802           fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
1803                    real_progname);
1804           exit (1);
1805         }
1806     }
1807
1808   /* Load the init file, which may end up consulting the X resource database
1809      and the site-wide app-defaults file.  Note that at this point, it's
1810      important that `progname' be "xscreensaver", rather than whatever
1811      was in argv[0].
1812    */
1813   p->db = db;
1814   load_init_file (p);
1815   *p2 = *p;
1816
1817   /* Now that Xt has been initialized, and the resources have been read,
1818      we can set our `progname' variable to something more in line with
1819      reality.
1820    */
1821   progname = real_progname;
1822
1823
1824 #if 0
1825   /* Print out all the resources we read. */
1826   {
1827     XrmName name = { 0 };
1828     XrmClass class = { 0 };
1829     int count = 0;
1830     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
1831                           (POINTER) &count);
1832   }
1833 #endif
1834
1835
1836   /* Intern the atoms that xscreensaver_command() needs.
1837    */
1838   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
1839   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
1840   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
1841   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
1842   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
1843   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
1844   XA_SELECT = XInternAtom (dpy, "SELECT", False);
1845   XA_DEMO = XInternAtom (dpy, "DEMO", False);
1846   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
1847   XA_BLANK = XInternAtom (dpy, "BLANK", False);
1848   XA_LOCK = XInternAtom (dpy, "LOCK", False);
1849   XA_EXIT = XInternAtom (dpy, "EXIT", False);
1850   XA_RESTART = XInternAtom (dpy, "RESTART", False);
1851
1852
1853   /* Create the window and all its widgets.
1854    */
1855   gtk_window = create_xscreensaver_demo ();
1856
1857   /* Set the window's title. */
1858   {
1859     char title[255];
1860     char *v = (char *) strdup(strchr(screensaver_id, ' '));
1861     char *s1, *s2, *s3, *s4;
1862     s1 = (char *) strchr(v,  ' '); s1++;
1863     s2 = (char *) strchr(s1, ' ');
1864     s3 = (char *) strchr(v,  '('); s3++;
1865     s4 = (char *) strchr(s3, ')');
1866     *s2 = 0;
1867     *s4 = 0;
1868     sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
1869     gtk_window_set_title (GTK_WINDOW (gtk_window), title);
1870     free (v);
1871   }
1872
1873   /* Various other widget initializations...
1874    */
1875   gtk_signal_connect (GTK_OBJECT (gtk_window), "delete_event",
1876                       GTK_SIGNAL_FUNC (wm_close_cb), NULL);
1877
1878   populate_hack_list (gtk_window, pair);
1879   populate_prefs_page (gtk_window, pair);
1880   sensitize_demo_widgets (gtk_window, False);
1881   fix_text_entry_sizes (gtk_window);
1882   scroll_to_current_hack (gtk_window, pair);
1883   gtk_widget_show (gtk_window);
1884
1885   /* The next three calls must come after gtk_widget_show(). */
1886   pixmapify_buttons (gtk_window);
1887   eschew_gtk_lossage (gtk_window);
1888   ensure_selected_item_visible (GTK_WIDGET(name_to_widget(gtk_window,"list")));
1889
1890   /* Handle the -prefs command-line argument. */
1891   if (prefs)
1892     {
1893       GtkNotebook *notebook =
1894         GTK_NOTEBOOK (name_to_widget (gtk_window, "notebook"));
1895       gtk_notebook_set_page (notebook, 1);
1896     }
1897
1898   /* Issue any warnings about the running xscreensaver daemon. */
1899   the_network_is_not_the_computer (gtk_window);
1900
1901   /* Run the Gtk event loop, and not the Xt event loop.  This means that
1902      if there were Xt timers or fds registered, they would never get serviced,
1903      and if there were any Xt widgets, they would never have events delivered.
1904      Fortunately, we're using Gtk for all of the UI, and only initialized
1905      Xt so that we could process the command line and use the X resource
1906      manager.
1907    */
1908   gtk_main ();
1909   exit (0);
1910 }
1911
1912 #endif /* HAVE_GTK -- whole file */