http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2006 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 <xscreensaver-intl.h>
20
21 #include <stdlib.h>
22
23 #ifdef HAVE_UNISTD_H
24 # include <unistd.h>
25 #endif
26
27 # ifdef __GNUC__
28 #  define STFU __extension__  /* ignore gcc -pendantic warnings in next sexp */
29 # else
30 #  define STFU /* */
31 # endif
32
33
34 #ifdef ENABLE_NLS
35 # include <locale.h>
36 #endif /* ENABLE_NLS */
37
38 #ifndef VMS
39 # include <pwd.h>               /* for getpwuid() */
40 #else /* VMS */
41 # include "vms-pwd.h"
42 #endif /* VMS */
43
44 #ifdef HAVE_UNAME
45 # include <sys/utsname.h>       /* for uname() */
46 #endif /* HAVE_UNAME */
47
48 #include <stdio.h>
49 #include <time.h>
50 #include <sys/stat.h>
51 #include <sys/time.h>
52
53
54 #include <signal.h>
55 #include <errno.h>
56 #ifdef HAVE_SYS_WAIT_H
57 # include <sys/wait.h>          /* for waitpid() and associated macros */
58 #endif
59
60
61 #include <X11/Xproto.h>         /* for CARD32 */
62 #include <X11/Xatom.h>          /* for XA_INTEGER */
63 #include <X11/Intrinsic.h>
64 #include <X11/StringDefs.h>
65
66 /* We don't actually use any widget internals, but these are included
67    so that gdb will have debug info for the widgets... */
68 #include <X11/IntrinsicP.h>
69 #include <X11/ShellP.h>
70
71 #ifdef HAVE_XMU
72 # ifndef VMS
73 #  include <X11/Xmu/Error.h>
74 # else /* VMS */
75 #  include <Xmu/Error.h>
76 # endif
77 #else
78 # include "xmu.h"
79 #endif
80
81 #ifdef HAVE_XINERAMA
82 # include <X11/extensions/Xinerama.h>
83 #endif /* HAVE_XINERAMA */
84
85 #include <gtk/gtk.h>
86
87 #ifdef HAVE_CRAPPLET
88 # include <gnome.h>
89 # include <capplet-widget.h>
90 #endif
91
92 #include <gdk/gdkx.h>
93
94 #ifdef HAVE_GTK2
95 # include <glade/glade-xml.h>
96 # include <gmodule.h>
97 #else  /* !HAVE_GTK2 */
98 # define G_MODULE_EXPORT /**/
99 #endif /* !HAVE_GTK2 */
100
101 #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
102 # define GLADE_DIR DEFAULT_ICONDIR
103 #endif
104 #if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
105 # define DEFAULT_ICONDIR GLADE_DIR
106 #endif
107
108 #ifndef HAVE_XML
109  /* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML.
110     It is unused otherwise, so in that case, stub it out. */
111  static const char *hack_configuration_path = 0;
112 #endif
113
114
115
116 #include "version.h"
117 #include "prefs.h"
118 #include "resources.h"          /* for parse_time() */
119 #include "visual.h"             /* for has_writable_cells() */
120 #include "remote.h"             /* for xscreensaver_command() */
121 #include "usleep.h"
122
123 #include "logo-50.xpm"
124 #include "logo-180.xpm"
125
126 #undef dgettext  /* else these are defined twice... */
127 #undef dcgettext
128
129 #include "demo-Gtk-widgets.h"
130 #include "demo-Gtk-support.h"
131 #include "demo-Gtk-conf.h"
132
133 #include <stdio.h>
134 #include <string.h>
135 #include <ctype.h>
136
137 #ifdef HAVE_GTK2
138 enum {
139   COL_ENABLED,
140   COL_NAME,
141   COL_LAST
142 };
143 #endif /* HAVE_GTK2 */
144
145 /* from exec.c */
146 extern void exec_command (const char *shell, const char *command, int nice);
147 extern int on_path_p (const char *program);
148
149 static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
150
151 #undef countof
152 #define countof(x) (sizeof((x))/sizeof((*x)))
153
154
155 char *progname = 0;
156 char *progclass = "XScreenSaver";
157 XrmDatabase db;
158
159 /* The order of the items in the mode menu. */
160 static int mode_menu_order[] = {
161   DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME };
162
163
164 typedef struct {
165
166   char *short_version;          /* version number of this xscreensaver build */
167
168   GtkWidget *toplevel_widget;   /* the main window */
169   GtkWidget *base_widget;       /* root of our hierarchy (for name lookups) */
170   GtkWidget *popup_widget;      /* the "Settings" dialog */
171   conf_data *cdata;             /* private data for per-hack configuration */
172
173 #ifdef HAVE_GTK2
174   GladeXML *glade_ui;           /* Glade UI file */
175 #endif /* HAVE_GTK2 */
176
177   Bool debug_p;                 /* whether to print diagnostics */
178   Bool initializing_p;          /* flag for breaking recursion loops */
179   Bool saving_p;                /* flag for breaking recursion loops */
180
181   char *desired_preview_cmd;    /* subprocess we intend to run */
182   char *running_preview_cmd;    /* subprocess we are currently running */
183   pid_t running_preview_pid;    /* pid of forked subproc (might be dead) */
184   Bool running_preview_error_p; /* whether the pid died abnormally */
185
186   Bool preview_suppressed_p;    /* flag meaning "don't launch subproc" */
187   int subproc_timer_id;         /* timer to delay subproc launch */
188   int subproc_check_timer_id;   /* timer to check whether it started up */
189   int subproc_check_countdown;  /* how many more checks left */
190
191   int *list_elt_to_hack_number; /* table for sorting the hack list */
192   int *hack_number_to_list_elt; /* the inverse table */
193   Bool *hacks_available_p;      /* whether hacks are on $PATH */
194   int total_available;          /* how many are on $PATH */
195   int list_count;               /* how many items are in the list: this may be
196                                    less than p->screenhacks_count, if some are
197                                    suppressed. */
198
199   int _selected_list_element;   /* don't use this: call
200                                    selected_list_element() instead */
201
202   int nscreens;                 /* How many X or Xinerama screens there are */
203
204   saver_preferences prefs;
205
206 } state;
207
208
209 /* Total fucking evilness due to the fact that it's rocket science to get
210    a closure object of our own down into the various widget callbacks. */
211 static state *global_state_kludge;
212
213 Atom XA_VROOT;
214 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
215 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
216 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
217
218
219 static void populate_demo_window (state *, int list_elt);
220 static void populate_prefs_page (state *);
221 static void populate_popup_window (state *);
222
223 static Bool flush_dialog_changes_and_save (state *);
224 static Bool flush_popup_changes_and_save (state *);
225
226 static int maybe_reload_init_file (state *);
227 static void await_xscreensaver (state *);
228 static Bool xscreensaver_running_p (state *);
229 static void sensitize_menu_items (state *s, Bool force_p);
230 static void force_dialog_repaint (state *s);
231
232 static void schedule_preview (state *, const char *cmd);
233 static void kill_preview_subproc (state *, Bool reset_p);
234 static void schedule_preview_check (state *);
235
236 \f
237 /* Prototypes of functions used by the Glade-generated code,
238    to avoid warnings.
239  */
240 void exit_menu_cb (GtkMenuItem *, gpointer user_data);
241 void about_menu_cb (GtkMenuItem *, gpointer user_data);
242 void doc_menu_cb (GtkMenuItem *, gpointer user_data);
243 void file_menu_cb (GtkMenuItem *, gpointer user_data);
244 void activate_menu_cb (GtkMenuItem *, gpointer user_data);
245 void lock_menu_cb (GtkMenuItem *, gpointer user_data);
246 void kill_menu_cb (GtkMenuItem *, gpointer user_data);
247 void restart_menu_cb (GtkWidget *, gpointer user_data);
248 void run_this_cb (GtkButton *, gpointer user_data);
249 void manual_cb (GtkButton *, gpointer user_data);
250 void run_next_cb (GtkButton *, gpointer user_data);
251 void run_prev_cb (GtkButton *, gpointer user_data);
252 void pref_changed_cb (GtkWidget *, gpointer user_data);
253 gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data);
254 void mode_menu_item_cb (GtkWidget *, gpointer user_data);
255 void switch_page_cb (GtkNotebook *, GtkNotebookPage *, 
256                      gint page_num, gpointer user_data);
257 void browse_image_dir_cb (GtkButton *, gpointer user_data);
258 void browse_text_file_cb (GtkButton *, gpointer user_data);
259 void browse_text_program_cb (GtkButton *, gpointer user_data);
260 void settings_cb (GtkButton *, gpointer user_data);
261 void settings_adv_cb (GtkButton *, gpointer user_data);
262 void settings_std_cb (GtkButton *, gpointer user_data);
263 void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *,
264                               gint page_num, gpointer user_data);
265 void settings_cancel_cb (GtkButton *, gpointer user_data);
266 void settings_ok_cb (GtkButton *, gpointer user_data);
267
268 \f
269 /* Some random utility functions
270  */
271
272 const char *blurb (void);
273
274 const char *
275 blurb (void)
276 {
277   time_t now = time ((time_t *) 0);
278   char *ct = (char *) ctime (&now);
279   static char buf[255];
280   int n = strlen(progname);
281   if (n > 100) n = 99;
282   strncpy(buf, progname, n);
283   buf[n++] = ':';
284   buf[n++] = ' ';
285   strncpy(buf+n, ct+11, 8);
286   strcpy(buf+n+9, ": ");
287   return buf;
288 }
289
290
291 static GtkWidget *
292 name_to_widget (state *s, const char *name)
293 {
294   GtkWidget *w;
295   if (!s) abort();
296   if (!name) abort();
297   if (!*name) abort();
298
299 #ifdef HAVE_GTK2
300   if (!s->glade_ui)
301     {
302       /* First try to load the Glade file from the current directory;
303          if there isn't one there, check the installed directory.
304        */
305 # define GLADE_FILE_NAME "xscreensaver-demo.glade2"
306       const char * const files[] = { GLADE_FILE_NAME,
307                                      GLADE_DIR "/" GLADE_FILE_NAME };
308       int i;
309       for (i = 0; i < countof (files); i++)
310         {
311           struct stat st;
312           if (!stat (files[i], &st))
313             {
314               s->glade_ui = glade_xml_new (files[i], NULL, NULL);
315               break;
316             }
317         }
318       if (!s->glade_ui)
319         {
320           fprintf (stderr,
321                    "%s: could not load \"" GLADE_FILE_NAME "\"\n"
322                    "\tfrom " GLADE_DIR "/ or current directory.\n",
323                    blurb());
324           exit (-1);
325         }
326 # undef GLADE_FILE_NAME
327
328       glade_xml_signal_autoconnect (s->glade_ui);
329     }
330
331   w = glade_xml_get_widget (s->glade_ui, name);
332
333 #else /* !HAVE_GTK2 */
334
335   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
336                                          name);
337   if (w) return w;
338   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
339                                          name);
340 #endif /* HAVE_GTK2 */
341   if (w) return w;
342
343   fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n",
344            blurb(), name);
345   abort();
346 }
347
348
349 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
350    Takes a scroller, viewport, or list as an argument.
351  */
352 static void
353 ensure_selected_item_visible (GtkWidget *widget)
354 {
355 #ifdef HAVE_GTK2
356   GtkTreePath *path;
357   GtkTreeSelection *selection;
358   GtkTreeIter iter;
359   GtkTreeModel *model;
360   
361   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
362   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
363         path = gtk_tree_path_new_first ();
364   else
365         path = gtk_tree_model_get_path (model, &iter);
366   
367   gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
368
369   gtk_tree_path_free (path);
370
371 #else /* !HAVE_GTK2 */
372
373   GtkScrolledWindow *scroller = 0;
374   GtkViewport *vp = 0;
375   GtkList *list_widget = 0;
376   GList *slist;
377   GList *kids;
378   int nkids = 0;
379   GtkWidget *selected = 0;
380   int list_elt = -1;
381   GtkAdjustment *adj;
382   gint parent_h, child_y, child_h, children_h, ignore;
383   double ratio_t, ratio_b;
384
385   if (GTK_IS_SCROLLED_WINDOW (widget))
386     {
387       scroller = GTK_SCROLLED_WINDOW (widget);
388       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
389       list_widget = GTK_LIST (GTK_BIN(vp)->child);
390     }
391   else if (GTK_IS_VIEWPORT (widget))
392     {
393       vp = GTK_VIEWPORT (widget);
394       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
395       list_widget = GTK_LIST (GTK_BIN(vp)->child);
396     }
397   else if (GTK_IS_LIST (widget))
398     {
399       list_widget = GTK_LIST (widget);
400       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
401       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
402     }
403   else
404     abort();
405
406   slist = list_widget->selection;
407   selected = (slist ? GTK_WIDGET (slist->data) : 0);
408   if (!selected)
409     return;
410
411   list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
412
413   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
414        kids; kids = kids->next)
415     nkids++;
416
417   adj = gtk_scrolled_window_get_vadjustment (scroller);
418
419   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
420                            &ignore, &ignore, &ignore, &parent_h, &ignore);
421   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
422                            &ignore, &child_y, &ignore, &child_h, &ignore);
423   children_h = nkids * child_h;
424
425   ratio_t = ((double) child_y) / ((double) children_h);
426   ratio_b = ((double) child_y + child_h) / ((double) children_h);
427
428   if (adj->upper == 0.0)  /* no items in list */
429     return;
430
431   if (ratio_t < (adj->value / adj->upper) ||
432       ratio_b > ((adj->value + adj->page_size) / adj->upper))
433     {
434       double target;
435       int slop = parent_h * 0.75; /* how much to overshoot by */
436
437       if (ratio_t < (adj->value / adj->upper))
438         {
439           double ratio_w = ((double) parent_h) / ((double) children_h);
440           double ratio_l = (ratio_b - ratio_t);
441           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
442           target += slop;
443         }
444       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
445         {
446           target = ratio_t * adj->upper;
447           target -= slop;
448         }
449
450       if (target > adj->upper - adj->page_size)
451         target = adj->upper - adj->page_size;
452       if (target < 0)
453         target = 0;
454
455       gtk_adjustment_set_value (adj, target);
456     }
457 #endif /* !HAVE_GTK2 */
458 }
459
460 static void
461 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
462 {
463   GtkWidget *shell = GTK_WIDGET (user_data);
464   while (shell->parent)
465     shell = shell->parent;
466   gtk_widget_destroy (GTK_WIDGET (shell));
467 }
468
469
470 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
471
472 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
473 {
474   restart_menu_cb (widget, user_data);
475   warning_dialog_dismiss_cb (widget, user_data);
476 }
477
478 static void
479 warning_dialog (GtkWidget *parent, const char *message,
480                 Boolean restart_button_p, int center)
481 {
482   char *msg = strdup (message);
483   char *head;
484
485   GtkWidget *dialog = gtk_dialog_new ();
486   GtkWidget *label = 0;
487   GtkWidget *ok = 0;
488   GtkWidget *cancel = 0;
489   int i = 0;
490
491   while (parent && !parent->window)
492     parent = parent->parent;
493
494   if (!parent ||
495       !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
496     {
497       fprintf (stderr, "%s: too early for dialog?\n", progname);
498       return;
499     }
500
501   head = msg;
502   while (head)
503     {
504       char name[20];
505       char *s = strchr (head, '\n');
506       if (s) *s = 0;
507
508       sprintf (name, "label%d", i++);
509
510       {
511         label = gtk_label_new (head);
512 #ifdef HAVE_GTK2
513         gtk_label_set_selectable (GTK_LABEL (label), TRUE);
514 #endif /* HAVE_GTK2 */
515
516 #ifndef HAVE_GTK2
517         if (i == 1)
518           {
519             GTK_WIDGET (label)->style =
520               gtk_style_copy (GTK_WIDGET (label)->style);
521             GTK_WIDGET (label)->style->font =
522               gdk_font_load (get_string_resource("warning_dialog.headingFont",
523                                                  "Dialog.Font"));
524             gtk_widget_set_style (GTK_WIDGET (label),
525                                   GTK_WIDGET (label)->style);
526           }
527 #endif /* !HAVE_GTK2 */
528         if (center <= 0)
529           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
530         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
531                             label, TRUE, TRUE, 0);
532         gtk_widget_show (label);
533       }
534
535       if (s)
536         head = s+1;
537       else
538         head = 0;
539
540       center--;
541     }
542
543   label = gtk_label_new ("");
544   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
545                       label, TRUE, TRUE, 0);
546   gtk_widget_show (label);
547
548   label = gtk_hbutton_box_new ();
549   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
550                       label, TRUE, TRUE, 0);
551
552 #ifdef HAVE_GTK2
553   if (restart_button_p)
554     {
555       cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
556       gtk_container_add (GTK_CONTAINER (label), cancel);
557     }
558
559   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
560   gtk_container_add (GTK_CONTAINER (label), ok);
561
562 #else /* !HAVE_GTK2 */
563
564   ok = gtk_button_new_with_label ("OK");
565   gtk_container_add (GTK_CONTAINER (label), ok);
566
567   if (restart_button_p)
568     {
569       cancel = gtk_button_new_with_label ("Cancel");
570       gtk_container_add (GTK_CONTAINER (label), cancel);
571     }
572
573 #endif /* !HAVE_GTK2 */
574
575   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
576   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
577   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
578   STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
579   gtk_widget_show (ok);
580   gtk_widget_grab_focus (ok);
581
582   if (cancel)
583     {
584       STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); 
585       gtk_widget_show (cancel);
586     }
587   gtk_widget_show (label);
588   gtk_widget_show (dialog);
589
590   if (restart_button_p)
591     {
592       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
593                                  GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
594                                  (gpointer) dialog);
595       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
596                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
597                                  (gpointer) dialog);
598     }
599   else
600     {
601       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
602                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
603                                  (gpointer) dialog);
604     }
605
606   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
607                                 GTK_WIDGET (parent)->window);
608
609 #ifdef HAVE_GTK2
610   gtk_window_present (GTK_WINDOW (dialog));
611 #else  /* !HAVE_GTK2 */
612   gdk_window_show (GTK_WIDGET (dialog)->window);
613   gdk_window_raise (GTK_WIDGET (dialog)->window);
614 #endif /* !HAVE_GTK2 */
615
616   free (msg);
617 }
618
619
620 static void
621 run_cmd (state *s, Atom command, int arg)
622 {
623   char *err = 0;
624   int status;
625
626   flush_dialog_changes_and_save (s);
627   status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
628
629   /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
630   if (status < 0 && err && strstr (err, "unexpectedly deleted"))
631     status = 0;
632
633   if (status < 0)
634     {
635       char buf [255];
636       if (err)
637         sprintf (buf, "Error:\n\n%s", err);
638       else
639         strcpy (buf, "Unknown error!");
640       warning_dialog (s->toplevel_widget, buf, False, 100);
641     }
642   if (err) free (err);
643
644   sensitize_menu_items (s, True);
645   force_dialog_repaint (s);
646 }
647
648
649 static void
650 run_hack (state *s, int list_elt, Bool report_errors_p)
651 {
652   int hack_number;
653   char *err = 0;
654   int status;
655
656   if (list_elt < 0) return;
657   hack_number = s->list_elt_to_hack_number[list_elt];
658
659   flush_dialog_changes_and_save (s);
660   schedule_preview (s, 0);
661
662   status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
663                                  False, &err);
664
665   if (status < 0 && report_errors_p)
666     {
667       if (xscreensaver_running_p (s))
668         {
669           /* Kludge: ignore the spurious "window unexpectedly deleted"
670              errors... */
671           if (err && strstr (err, "unexpectedly deleted"))
672             status = 0;
673
674           if (status < 0)
675             {
676               char buf [255];
677               if (err)
678                 sprintf (buf, "Error:\n\n%s", err);
679               else
680                 strcpy (buf, "Unknown error!");
681               warning_dialog (s->toplevel_widget, buf, False, 100);
682             }
683         }
684       else
685         {
686           /* The error is that the daemon isn't running;
687              offer to restart it.
688            */
689           const char *d = DisplayString (GDK_DISPLAY());
690           char msg [1024];
691           sprintf (msg,
692                    _("Warning:\n\n"
693                      "The XScreenSaver daemon doesn't seem to be running\n"
694                      "on display \"%s\".  Launch it now?"),
695                    d);
696           warning_dialog (s->toplevel_widget, msg, True, 1);
697         }
698     }
699
700   if (err) free (err);
701
702   sensitize_menu_items (s, False);
703 }
704
705
706 \f
707 /* Button callbacks
708
709    According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
710    libglade work on Cygwin; apparently all Glade callbacks need this magic
711    extra declaration.  I do not pretend to understand.
712  */
713
714 G_MODULE_EXPORT void
715 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
716 {
717   state *s = global_state_kludge;  /* I hate C so much... */
718   flush_dialog_changes_and_save (s);
719   kill_preview_subproc (s, False);
720   gtk_main_quit ();
721 }
722
723 static gboolean
724 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
725 {
726   state *s = (state *) data;
727   flush_dialog_changes_and_save (s);
728   gtk_main_quit ();
729   return TRUE;
730 }
731
732
733 G_MODULE_EXPORT void
734 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
735 {
736   char msg [2048];
737   char *vers = strdup (screensaver_id + 4);
738   char *s;
739   char copy[1024];
740   char *desc = _("For updates, check http://www.jwz.org/xscreensaver/");
741
742   s = strchr (vers, ',');
743   *s = 0;
744   s += 2;
745
746   /* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
747      non-ASCII characters aren't allowed in localizable string keys."
748      (I don't want to just use (c) instead of Â© because that doesn't
749      look as good in the plain-old default Latin1 "C" locale.)
750    */
751 #ifdef HAVE_GTK2
752   sprintf(copy, ("Copyright \xC2\xA9 1991-2006 %s"), s);
753 #else  /* !HAVE_GTK2 */
754   sprintf(copy, ("Copyright \251 1991-2006 %s"), s);
755 #endif /* !HAVE_GTK2 */
756
757   sprintf (msg, "%s\n\n%s", copy, desc);
758
759   /* I can't make gnome_about_new() work here -- it starts dying in
760      gdk_imlib_get_visual() under gnome_about_new().  If this worked,
761      then this might be the thing to do:
762
763      #ifdef HAVE_CRAPPLET
764      {
765        const gchar *auth[] = { 0 };
766        GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
767                                            "xscreensaver.xpm");
768        gtk_widget_show (about);
769      }
770      #else / * GTK but not GNOME * /
771       ...
772    */
773   {
774     GdkColormap *colormap;
775     GdkPixmap *gdkpixmap;
776     GdkBitmap *mask;
777
778     GtkWidget *dialog = gtk_dialog_new ();
779     GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
780     GtkWidget *parent = GTK_WIDGET (menuitem);
781     while (parent->parent)
782       parent = parent->parent;
783
784     hbox = gtk_hbox_new (FALSE, 20);
785     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
786                         hbox, TRUE, TRUE, 0);
787
788     colormap = gtk_widget_get_colormap (parent);
789     gdkpixmap =
790       gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
791                                              (gchar **) logo_180_xpm);
792     icon = gtk_pixmap_new (gdkpixmap, mask);
793     gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
794
795     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
796
797     vbox = gtk_vbox_new (FALSE, 0);
798     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
799
800     label1 = gtk_label_new (vers);
801     gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
802     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
803     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
804
805 #ifndef HAVE_GTK2
806     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
807     GTK_WIDGET (label1)->style->font =
808       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
809     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
810 #endif /* HAVE_GTK2 */
811
812     label2 = gtk_label_new (msg);
813     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
814     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
815     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
816
817 #ifndef HAVE_GTK2
818     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
819     GTK_WIDGET (label2)->style->font =
820       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
821     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
822 #endif /* HAVE_GTK2 */
823
824     hb = gtk_hbutton_box_new ();
825
826     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
827                         hb, TRUE, TRUE, 0);
828
829 #ifdef HAVE_GTK2
830     ok = gtk_button_new_from_stock (GTK_STOCK_OK);
831 #else /* !HAVE_GTK2 */
832     ok = gtk_button_new_with_label (_("OK"));
833 #endif /* !HAVE_GTK2 */
834     gtk_container_add (GTK_CONTAINER (hb), ok);
835
836     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
837     gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
838     gtk_window_set_title (GTK_WINDOW (dialog), progclass);
839
840     gtk_widget_show (hbox);
841     gtk_widget_show (icon);
842     gtk_widget_show (vbox);
843     gtk_widget_show (label1);
844     gtk_widget_show (label2);
845     gtk_widget_show (hb);
846     gtk_widget_show (ok);
847     gtk_widget_show (dialog);
848
849     gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
850                                GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
851                                (gpointer) dialog);
852     gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
853                                   GTK_WIDGET (parent)->window);
854     gdk_window_show (GTK_WIDGET (dialog)->window);
855     gdk_window_raise (GTK_WIDGET (dialog)->window);
856   }
857 }
858
859
860 G_MODULE_EXPORT void
861 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
862 {
863   state *s = global_state_kludge;  /* I hate C so much... */
864   saver_preferences *p = &s->prefs;
865   char *help_command;
866
867   if (!p->help_url || !*p->help_url)
868     {
869       warning_dialog (s->toplevel_widget,
870                       _("Error:\n\n"
871                         "No Help URL has been specified.\n"), False, 100);
872       return;
873     }
874
875   help_command = (char *) malloc (strlen (p->load_url_command) +
876                                   (strlen (p->help_url) * 4) + 20);
877   strcpy (help_command, "( ");
878   sprintf (help_command + strlen(help_command),
879            p->load_url_command,
880            p->help_url, p->help_url, p->help_url, p->help_url);
881   strcat (help_command, " ) &");
882   if (system (help_command) < 0)
883     fprintf (stderr, "%s: fork error\n", blurb());
884   free (help_command);
885 }
886
887
888 G_MODULE_EXPORT void
889 file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
890 {
891   state *s = global_state_kludge;  /* I hate C so much... */
892   sensitize_menu_items (s, False);
893 }
894
895
896 G_MODULE_EXPORT void
897 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
898 {
899   state *s = global_state_kludge;  /* I hate C so much... */
900   run_cmd (s, XA_ACTIVATE, 0);
901 }
902
903
904 G_MODULE_EXPORT void
905 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
906 {
907   state *s = global_state_kludge;  /* I hate C so much... */
908   run_cmd (s, XA_LOCK, 0);
909 }
910
911
912 G_MODULE_EXPORT void
913 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
914 {
915   state *s = global_state_kludge;  /* I hate C so much... */
916   run_cmd (s, XA_EXIT, 0);
917 }
918
919
920 G_MODULE_EXPORT void
921 restart_menu_cb (GtkWidget *widget, gpointer user_data)
922 {
923   state *s = global_state_kludge;  /* I hate C so much... */
924   flush_dialog_changes_and_save (s);
925   xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
926   sleep (1);
927   if (system ("xscreensaver -nosplash &") < 0)
928     fprintf (stderr, "%s: fork error\n", blurb());
929
930   await_xscreensaver (s);
931 }
932
933 static Bool
934 xscreensaver_running_p (state *s)
935 {
936   Display *dpy = GDK_DISPLAY();
937   char *rversion = 0;
938   server_xscreensaver_version (dpy, &rversion, 0, 0);
939   if (!rversion)
940     return False;
941   free (rversion);
942   return True;
943 }
944
945 static void
946 await_xscreensaver (state *s)
947 {
948   int countdown = 5;
949   Bool ok = False;
950
951   while (!ok && (--countdown > 0))
952     if (xscreensaver_running_p (s))
953       ok = True;
954     else
955       sleep (1);    /* If it's not there yet, wait a second... */
956
957   sensitize_menu_items (s, True);
958
959   if (! ok)
960     {
961       /* Timed out, no screensaver running. */
962
963       char buf [1024];
964       Bool root_p = (geteuid () == 0);
965       
966       strcpy (buf, 
967               _("Error:\n\n"
968                 "The xscreensaver daemon did not start up properly.\n"
969                 "\n"));
970
971       if (root_p)
972
973 # ifdef __GNUC__
974         __extension__     /* don't warn about "string length is greater than
975                              the length ISO C89 compilers are required to
976                              support" in the following expression... */
977 # endif
978         strcat (buf,
979           _("You are running as root.  This usually means that xscreensaver\n"
980             "was unable to contact your X server because access control is\n"
981             "turned on.  Try running this command:\n"
982             "\n"
983             "                        xhost +localhost\n"
984             "\n"
985             "and then selecting `File / Restart Daemon'.\n"
986             "\n"
987             "Note that turning off access control will allow anyone logged\n"
988             "on to this machine to access your screen, which might be\n"
989             "considered a security problem.  Please read the xscreensaver\n"
990             "manual and FAQ for more information.\n"
991             "\n"
992             "You shouldn't run X as root. Instead, you should log in as a\n"
993             "normal user, and `su' as necessary."));
994       else
995         strcat (buf, _("Please check your $PATH and permissions."));
996
997       warning_dialog (s->toplevel_widget, buf, False, 1);
998     }
999
1000   force_dialog_repaint (s);
1001 }
1002
1003
1004 static int
1005 selected_list_element (state *s)
1006 {
1007   return s->_selected_list_element;
1008 }
1009
1010
1011 static int
1012 demo_write_init_file (state *s, saver_preferences *p)
1013 {
1014   Display *dpy = GDK_DISPLAY();
1015
1016 #if 0
1017   /* #### try to figure out why shit keeps getting reordered... */
1018   if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow"))
1019     abort();
1020 #endif
1021
1022   if (!write_init_file (dpy, p, s->short_version, False))
1023     {
1024       if (s->debug_p)
1025         fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
1026       return 0;
1027     }
1028   else
1029     {
1030       const char *f = init_file_name();
1031       if (!f || !*f)
1032         warning_dialog (s->toplevel_widget,
1033                         _("Error:\n\nCouldn't determine init file name!\n"),
1034                         False, 100);
1035       else
1036         {
1037           char *b = (char *) malloc (strlen(f) + 1024);
1038           sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
1039           warning_dialog (s->toplevel_widget, b, False, 100);
1040           free (b);
1041         }
1042       return -1;
1043     }
1044 }
1045
1046
1047 G_MODULE_EXPORT void
1048 run_this_cb (GtkButton *button, gpointer user_data)
1049 {
1050   state *s = global_state_kludge;  /* I hate C so much... */
1051   int list_elt = selected_list_element (s);
1052   if (list_elt < 0) return;
1053   if (!flush_dialog_changes_and_save (s))
1054     run_hack (s, list_elt, True);
1055 }
1056
1057
1058 G_MODULE_EXPORT void
1059 manual_cb (GtkButton *button, gpointer user_data)
1060 {
1061   Display *dpy = GDK_DISPLAY();
1062   state *s = global_state_kludge;  /* I hate C so much... */
1063   saver_preferences *p = &s->prefs;
1064   GtkWidget *list_widget = name_to_widget (s, "list");
1065   int list_elt = selected_list_element (s);
1066   int hack_number;
1067   char *name, *name2, *cmd, *str;
1068   char *oname = 0;
1069   if (list_elt < 0) return;
1070   hack_number = s->list_elt_to_hack_number[list_elt];
1071
1072   flush_dialog_changes_and_save (s);
1073   ensure_selected_item_visible (list_widget);
1074
1075   name = strdup (p->screenhacks[hack_number]->command);
1076   name2 = name;
1077   oname = name;
1078   while (isspace (*name2)) name2++;
1079   str = name2;
1080   while (*str && !isspace (*str)) str++;
1081   *str = 0;
1082   str = strrchr (name2, '/');
1083   if (str) name2 = str+1;
1084
1085   cmd = get_string_resource (dpy, "manualCommand", "ManualCommand");
1086   if (cmd)
1087     {
1088       char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
1089       strcpy (cmd2, "( ");
1090       sprintf (cmd2 + strlen (cmd2),
1091                cmd,
1092                name2, name2, name2, name2);
1093       strcat (cmd2, " ) &");
1094       if (system (cmd2) < 0)
1095         fprintf (stderr, "%s: fork error\n", blurb());
1096       free (cmd2);
1097     }
1098   else
1099     {
1100       warning_dialog (GTK_WIDGET (button),
1101                       _("Error:\n\nno `manualCommand' resource set."),
1102                       False, 100);
1103     }
1104
1105   free (oname);
1106 }
1107
1108
1109 static void
1110 force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
1111 {
1112   GtkWidget *parent = name_to_widget (s, "scroller");
1113   Bool was = GTK_WIDGET_IS_SENSITIVE (parent);
1114 #ifdef HAVE_GTK2
1115   GtkTreeIter iter;
1116   GtkTreeModel *model;
1117   GtkTreeSelection *selection;
1118 #endif /* HAVE_GTK2 */
1119
1120   if (!was) gtk_widget_set_sensitive (parent, True);
1121 #ifdef HAVE_GTK2
1122   model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1123   STFU g_assert (model);
1124   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
1125     {
1126       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
1127       gtk_tree_selection_select_iter (selection, &iter);
1128     }
1129 #else  /* !HAVE_GTK2 */
1130   gtk_list_select_item (GTK_LIST (list), list_elt);
1131 #endif /* !HAVE_GTK2 */
1132   if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
1133   if (!was) gtk_widget_set_sensitive (parent, False);
1134 }
1135
1136
1137 G_MODULE_EXPORT void
1138 run_next_cb (GtkButton *button, gpointer user_data)
1139 {
1140   state *s = global_state_kludge;  /* I hate C so much... */
1141   /* saver_preferences *p = &s->prefs; */
1142   Bool ops = s->preview_suppressed_p;
1143
1144   GtkWidget *list_widget = name_to_widget (s, "list");
1145   int list_elt = selected_list_element (s);
1146
1147   if (list_elt < 0)
1148     list_elt = 0;
1149   else
1150     list_elt++;
1151
1152   if (list_elt >= s->list_count)
1153     list_elt = 0;
1154
1155   s->preview_suppressed_p = True;
1156
1157   flush_dialog_changes_and_save (s);
1158   force_list_select_item (s, list_widget, list_elt, True);
1159   populate_demo_window (s, list_elt);
1160   run_hack (s, list_elt, False);
1161
1162   s->preview_suppressed_p = ops;
1163 }
1164
1165
1166 G_MODULE_EXPORT void
1167 run_prev_cb (GtkButton *button, gpointer user_data)
1168 {
1169   state *s = global_state_kludge;  /* I hate C so much... */
1170   /* saver_preferences *p = &s->prefs; */
1171   Bool ops = s->preview_suppressed_p;
1172
1173   GtkWidget *list_widget = name_to_widget (s, "list");
1174   int list_elt = selected_list_element (s);
1175
1176   if (list_elt < 0)
1177     list_elt = s->list_count - 1;
1178   else
1179     list_elt--;
1180
1181   if (list_elt < 0)
1182     list_elt = s->list_count - 1;
1183
1184   s->preview_suppressed_p = True;
1185
1186   flush_dialog_changes_and_save (s);
1187   force_list_select_item (s, list_widget, list_elt, True);
1188   populate_demo_window (s, list_elt);
1189   run_hack (s, list_elt, False);
1190
1191   s->preview_suppressed_p = ops;
1192 }
1193
1194
1195 /* Writes the given settings into prefs.
1196    Returns true if there was a change, False otherwise.
1197    command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
1198  */
1199 static Bool
1200 flush_changes (state *s,
1201                int list_elt,
1202                int enabled_p,
1203                const char *command,
1204                const char *visual)
1205 {
1206   saver_preferences *p = &s->prefs;
1207   Bool changed = False;
1208   screenhack *hack;
1209   int hack_number;
1210   if (list_elt < 0 || list_elt >= s->list_count)
1211     abort();
1212
1213   hack_number = s->list_elt_to_hack_number[list_elt];
1214   hack = p->screenhacks[hack_number];
1215
1216   if (enabled_p != -1 &&
1217       enabled_p != hack->enabled_p)
1218     {
1219       hack->enabled_p = enabled_p;
1220       changed = True;
1221       if (s->debug_p)
1222         fprintf (stderr, "%s: \"%s\": enabled => %d\n",
1223                  blurb(), hack->name, enabled_p);
1224     }
1225
1226   if (command)
1227     {
1228       if (!hack->command || !!strcmp (command, hack->command))
1229         {
1230           if (hack->command) free (hack->command);
1231           hack->command = strdup (command);
1232           changed = True;
1233           if (s->debug_p)
1234             fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
1235                      blurb(), hack->name, command);
1236         }
1237     }
1238
1239   if (visual)
1240     {
1241       const char *ov = hack->visual;
1242       if (!ov || !*ov) ov = "any";
1243       if (!*visual) visual = "any";
1244       if (!!strcasecmp (visual, ov))
1245         {
1246           if (hack->visual) free (hack->visual);
1247           hack->visual = strdup (visual);
1248           changed = True;
1249           if (s->debug_p)
1250             fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
1251                      blurb(), hack->name, visual);
1252         }
1253     }
1254
1255   return changed;
1256 }
1257
1258
1259 /* Helper for the text fields that contain time specifications:
1260    this parses the text, and does error checking.
1261  */
1262 static void 
1263 hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
1264 {
1265   if (*line)
1266     {
1267       int value;
1268       if (!sec_p || strchr (line, ':'))
1269         value = parse_time ((char *) line, sec_p, True);
1270       else
1271         {
1272           char c;
1273           if (sscanf (line, "%d%c", &value, &c) != 1)
1274             value = -1;
1275           if (!sec_p)
1276             value *= 60;
1277         }
1278
1279       value *= 1000;    /* Time measures in microseconds */
1280       if (value < 0)
1281         {
1282           char b[255];
1283           sprintf (b,
1284                    _("Error:\n\n"
1285                      "Unparsable time format: \"%s\"\n"),
1286                    line);
1287           warning_dialog (s->toplevel_widget, b, False, 100);
1288         }
1289       else
1290         *store = value;
1291     }
1292 }
1293
1294
1295 static Bool
1296 directory_p (const char *path)
1297 {
1298   struct stat st;
1299   if (!path || !*path)
1300     return False;
1301   else if (stat (path, &st))
1302     return False;
1303   else if (!S_ISDIR (st.st_mode))
1304     return False;
1305   else
1306     return True;
1307 }
1308
1309 static Bool
1310 file_p (const char *path)
1311 {
1312   struct stat st;
1313   if (!path || !*path)
1314     return False;
1315   else if (stat (path, &st))
1316     return False;
1317   else if (S_ISDIR (st.st_mode))
1318     return False;
1319   else
1320     return True;
1321 }
1322
1323 static char *
1324 normalize_directory (const char *path)
1325 {
1326   int L;
1327   char *p2, *s;
1328   if (!path || !*path) return 0;
1329   L = strlen (path);
1330   p2 = (char *) malloc (L + 2);
1331   strcpy (p2, path);
1332   if (p2[L-1] == '/')  /* remove trailing slash */
1333     p2[--L] = 0;
1334
1335   for (s = p2; s && *s; s++)
1336     {
1337       if (*s == '/' &&
1338           (!strncmp (s, "/../", 4) ||                   /* delete "XYZ/../" */
1339            !strncmp (s, "/..\000", 4)))                 /* delete "XYZ/..$" */
1340         {
1341           char *s0 = s;
1342           while (s0 > p2 && s0[-1] != '/')
1343             s0--;
1344           if (s0 > p2)
1345             {
1346               s0--;
1347               s += 3;
1348               strcpy (s0, s);
1349               s = s0-1;
1350             }
1351         }
1352       else if (*s == '/' && !strncmp (s, "/./", 3))     /* delete "/./" */
1353         strcpy (s, s+2), s--;
1354       else if (*s == '/' && !strncmp (s, "/.\000", 3))  /* delete "/.$" */
1355         *s = 0, s--;
1356     }
1357
1358   for (s = p2; s && *s; s++)            /* normalize consecutive slashes */
1359     while (s[0] == '/' && s[1] == '/')
1360       strcpy (s, s+1);
1361
1362   /* and strip trailing whitespace for good measure. */
1363   L = strlen(p2);
1364   while (isspace(p2[L-1]))
1365     p2[--L] = 0;
1366
1367   return p2;
1368 }
1369
1370
1371 #ifdef HAVE_GTK2
1372
1373 typedef struct {
1374   state *s;
1375   int i;
1376   Bool *changed;
1377 } FlushForeachClosure;
1378
1379 static gboolean
1380 flush_checkbox  (GtkTreeModel *model,
1381                  GtkTreePath *path,
1382                  GtkTreeIter *iter,
1383                  gpointer data)
1384 {
1385   FlushForeachClosure *closure = data;
1386   gboolean checked;
1387
1388   gtk_tree_model_get (model, iter,
1389                       COL_ENABLED, &checked,
1390                       -1);
1391
1392   if (flush_changes (closure->s, closure->i,
1393                      checked, 0, 0))
1394     *closure->changed = True;
1395   
1396   closure->i++;
1397
1398   /* don't remove row */
1399   return FALSE;
1400 }
1401
1402 #endif /* HAVE_GTK2 */
1403
1404 /* Flush out any changes made in the main dialog window (where changes
1405    take place immediately: clicking on a checkbox causes the init file
1406    to be written right away.)
1407  */
1408 static Bool
1409 flush_dialog_changes_and_save (state *s)
1410 {
1411   saver_preferences *p = &s->prefs;
1412   saver_preferences P2, *p2 = &P2;
1413 #ifdef HAVE_GTK2
1414   GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
1415   GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
1416   FlushForeachClosure closure;
1417 #else /* !HAVE_GTK2 */
1418   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
1419   GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
1420   int i;
1421 #endif /* !HAVE_GTK2 */
1422
1423   Bool changed = False;
1424   GtkWidget *w;
1425
1426   if (s->saving_p) return False;
1427   s->saving_p = True;
1428
1429   *p2 = *p;
1430
1431   /* Flush any checkbox changes in the list down into the prefs struct.
1432    */
1433 #ifdef HAVE_GTK2
1434   closure.s = s;
1435   closure.changed = &changed;
1436   closure.i = 0;
1437   gtk_tree_model_foreach (model, flush_checkbox, &closure);
1438
1439 #else /* !HAVE_GTK2 */
1440
1441   for (i = 0; kids; kids = kids->next, i++)
1442     {
1443       GtkWidget *line = GTK_WIDGET (kids->data);
1444       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1445       GtkWidget *line_check =
1446         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1447       Bool checked =
1448         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
1449
1450       if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
1451         changed = True;
1452     }
1453 #endif /* ~HAVE_GTK2 */
1454
1455   /* Flush the non-hack-specific settings down into the prefs struct.
1456    */
1457
1458 # define SECONDS(FIELD,NAME) \
1459     w = name_to_widget (s, (NAME)); \
1460     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
1461
1462 # define MINUTES(FIELD,NAME) \
1463     w = name_to_widget (s, (NAME)); \
1464     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
1465
1466 # define CHECKBOX(FIELD,NAME) \
1467     w = name_to_widget (s, (NAME)); \
1468     (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
1469
1470 # define PATHNAME(FIELD,NAME) \
1471     w = name_to_widget (s, (NAME)); \
1472     (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
1473
1474 # define TEXT(FIELD,NAME) \
1475     w = name_to_widget (s, (NAME)); \
1476     (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w))
1477
1478   MINUTES  (&p2->timeout,         "timeout_spinbutton");
1479   MINUTES  (&p2->cycle,           "cycle_spinbutton");
1480   CHECKBOX (p2->lock_p,           "lock_button");
1481   MINUTES  (&p2->lock_timeout,    "lock_spinbutton");
1482
1483   CHECKBOX (p2->dpms_enabled_p,  "dpms_button");
1484   MINUTES  (&p2->dpms_standby,    "dpms_standby_spinbutton");
1485   MINUTES  (&p2->dpms_suspend,    "dpms_suspend_spinbutton");
1486   MINUTES  (&p2->dpms_off,        "dpms_off_spinbutton");
1487
1488   CHECKBOX (p2->grab_desktop_p,   "grab_desk_button");
1489   CHECKBOX (p2->grab_video_p,     "grab_video_button");
1490   CHECKBOX (p2->random_image_p,   "grab_image_button");
1491   PATHNAME (p2->image_directory,  "image_text");
1492
1493 #if 0
1494   CHECKBOX (p2->verbose_p,        "verbose_button");
1495   CHECKBOX (p2->capture_stderr_p, "capture_button");
1496   CHECKBOX (p2->splash_p,         "splash_button");
1497 #endif
1498
1499   {
1500     Bool v = False;
1501     CHECKBOX (v, "text_host_radio");     if (v) p2->tmode = TEXT_DATE;
1502     CHECKBOX (v, "text_radio");          if (v) p2->tmode = TEXT_LITERAL;
1503     CHECKBOX (v, "text_file_radio");     if (v) p2->tmode = TEXT_FILE;
1504     CHECKBOX (v, "text_program_radio");  if (v) p2->tmode = TEXT_PROGRAM;
1505     CHECKBOX (v, "text_url_radio");      if (v) p2->tmode = TEXT_URL;
1506     TEXT     (p2->text_literal, "text_entry");
1507     PATHNAME (p2->text_file,    "text_file_entry");
1508     PATHNAME (p2->text_program, "text_program_entry");
1509     PATHNAME (p2->text_program, "text_program_entry");
1510     TEXT     (p2->text_url,     "text_url_entry");
1511   }
1512
1513   CHECKBOX (p2->install_cmap_p,   "install_button");
1514   CHECKBOX (p2->fade_p,           "fade_button");
1515   CHECKBOX (p2->unfade_p,         "unfade_button");
1516   SECONDS  (&p2->fade_seconds,    "fade_spinbutton");
1517
1518 # undef SECONDS
1519 # undef MINUTES
1520 # undef CHECKBOX
1521 # undef PATHNAME
1522 # undef TEXT
1523
1524   /* Warn if the image directory doesn't exist.
1525    */
1526   if (p2->image_directory &&
1527       *p2->image_directory &&
1528       !directory_p (p2->image_directory))
1529     {
1530       char b[255];
1531       sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n",
1532                p2->image_directory);
1533       warning_dialog (s->toplevel_widget, b, False, 100);
1534     }
1535
1536
1537   /* Map the mode menu to `saver_mode' enum values. */
1538   {
1539     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
1540     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1541     GtkWidget *selected = gtk_menu_get_active (menu);
1542     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1543     int menu_elt = g_list_index (kids, (gpointer) selected);
1544     if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
1545     p2->mode = mode_menu_order[menu_elt];
1546   }
1547
1548   if (p2->mode == ONE_HACK)
1549     {
1550       int list_elt = selected_list_element (s);
1551       p2->selected_hack = (list_elt >= 0
1552                            ? s->list_elt_to_hack_number[list_elt]
1553                            : -1);
1554     }
1555
1556 # define COPY(field, name) \
1557   if (p->field != p2->field) { \
1558     changed = True; \
1559     if (s->debug_p) \
1560       fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
1561   } \
1562   p->field = p2->field
1563
1564   COPY(mode,             "mode");
1565   COPY(selected_hack,    "selected_hack");
1566
1567   COPY(timeout,        "timeout");
1568   COPY(cycle,          "cycle");
1569   COPY(lock_p,         "lock_p");
1570   COPY(lock_timeout,   "lock_timeout");
1571
1572   COPY(dpms_enabled_p, "dpms_enabled_p");
1573   COPY(dpms_standby,   "dpms_standby");
1574   COPY(dpms_suspend,   "dpms_suspend");
1575   COPY(dpms_off,       "dpms_off");
1576
1577 #if 0
1578   COPY(verbose_p,        "verbose_p");
1579   COPY(capture_stderr_p, "capture_stderr_p");
1580   COPY(splash_p,         "splash_p");
1581 #endif
1582
1583   COPY(tmode,            "tmode");
1584
1585   COPY(install_cmap_p,   "install_cmap_p");
1586   COPY(fade_p,           "fade_p");
1587   COPY(unfade_p,         "unfade_p");
1588   COPY(fade_seconds,     "fade_seconds");
1589
1590   COPY(grab_desktop_p, "grab_desktop_p");
1591   COPY(grab_video_p,   "grab_video_p");
1592   COPY(random_image_p, "random_image_p");
1593
1594 # undef COPY
1595
1596 # define COPYSTR(FIELD,NAME) \
1597   if (!p->FIELD || \
1598       !p2->FIELD || \
1599       strcmp(p->FIELD, p2->FIELD)) \
1600     { \
1601       changed = True; \
1602       if (s->debug_p) \
1603         fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
1604     } \
1605   if (p->FIELD && p->FIELD != p2->FIELD) \
1606     free (p->FIELD); \
1607   p->FIELD = p2->FIELD; \
1608   p2->FIELD = 0
1609
1610   COPYSTR(image_directory, "image_directory");
1611   COPYSTR(text_literal,    "text_literal");
1612   COPYSTR(text_file,       "text_file");
1613   COPYSTR(text_program,    "text_program");
1614   COPYSTR(text_url,        "text_url");
1615 # undef COPYSTR
1616
1617   populate_prefs_page (s);
1618
1619   if (changed)
1620     {
1621       Display *dpy = GDK_DISPLAY();
1622       Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
1623       sync_server_dpms_settings (dpy, enabled_p,
1624                                  p->dpms_standby / 1000,
1625                                  p->dpms_suspend / 1000,
1626                                  p->dpms_off / 1000,
1627                                  False);
1628
1629       changed = demo_write_init_file (s, p);
1630     }
1631
1632   s->saving_p = False;
1633   return changed;
1634 }
1635
1636
1637 /* Flush out any changes made in the popup dialog box (where changes
1638    take place only when the OK button is clicked.)
1639  */
1640 static Bool
1641 flush_popup_changes_and_save (state *s)
1642 {
1643   Bool changed = False;
1644   saver_preferences *p = &s->prefs;
1645   int list_elt = selected_list_element (s);
1646
1647   GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
1648   GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
1649
1650   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
1651   const char *command = gtk_entry_get_text (cmd);
1652
1653   char c;
1654   unsigned long id;
1655
1656   if (s->saving_p) return False;
1657   s->saving_p = True;
1658
1659   if (list_elt < 0)
1660     goto DONE;
1661
1662   if (maybe_reload_init_file (s) != 0)
1663     {
1664       changed = True;
1665       goto DONE;
1666     }
1667
1668   /* Sanity-check and canonicalize whatever the user typed into the combo box.
1669    */
1670   if      (!strcasecmp (visual, ""))                   visual = "";
1671   else if (!strcasecmp (visual, "any"))                visual = "";
1672   else if (!strcasecmp (visual, "default"))            visual = "Default";
1673   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
1674   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
1675   else if (!strcasecmp (visual, "best"))               visual = "Best";
1676   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
1677   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
1678   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
1679   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
1680   else if (!strcasecmp (visual, "color"))              visual = "Color";
1681   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
1682   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
1683   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
1684   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
1685   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
1686   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
1687   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
1688   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
1689   else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
1690   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
1691   else
1692     {
1693       gdk_beep ();                                /* unparsable */
1694       visual = "";
1695       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
1696     }
1697
1698   changed = flush_changes (s, list_elt, -1, command, visual);
1699   if (changed)
1700     {
1701       changed = demo_write_init_file (s, p);
1702
1703       /* Do this to re-launch the hack if (and only if) the command line
1704          has changed. */
1705       populate_demo_window (s, selected_list_element (s));
1706     }
1707
1708  DONE:
1709   s->saving_p = False;
1710   return changed;
1711 }
1712
1713
1714 G_MODULE_EXPORT void
1715 pref_changed_cb (GtkWidget *widget, gpointer user_data)
1716 {
1717   state *s = global_state_kludge;  /* I hate C so much... */
1718   if (! s->initializing_p)
1719     {
1720       s->initializing_p = True;
1721       flush_dialog_changes_and_save (s);
1722       s->initializing_p = False;
1723     }
1724 }
1725
1726 G_MODULE_EXPORT gboolean
1727 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1728 {
1729   pref_changed_cb (widget, user_data);
1730   return FALSE;
1731 }
1732
1733 /* Callback on menu items in the "mode" options menu.
1734  */
1735 G_MODULE_EXPORT void
1736 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
1737 {
1738   state *s = (state *) user_data;
1739   saver_preferences *p = &s->prefs;
1740   GtkWidget *list = name_to_widget (s, "list");
1741   int list_elt;
1742
1743   GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent));
1744   int menu_index = 0;
1745   saver_mode new_mode;
1746
1747   while (menu_items)
1748     {
1749       if (menu_items->data == widget)
1750         break;
1751       menu_index++;
1752       menu_items = menu_items->next;
1753     }
1754   if (!menu_items) abort();
1755
1756   new_mode = mode_menu_order[menu_index];
1757
1758   /* Keep the same list element displayed as before; except if we're
1759      switching *to* "one screensaver" mode from any other mode, set
1760      "the one" to be that which is currently selected.
1761    */
1762   list_elt = selected_list_element (s);
1763   if (new_mode == ONE_HACK)
1764     p->selected_hack = s->list_elt_to_hack_number[list_elt];
1765
1766   {
1767     saver_mode old_mode = p->mode;
1768     p->mode = new_mode;
1769     populate_demo_window (s, list_elt);
1770     force_list_select_item (s, list, list_elt, True);
1771     p->mode = old_mode;  /* put it back, so the init file gets written */
1772   }
1773
1774   pref_changed_cb (widget, user_data);
1775 }
1776
1777
1778 G_MODULE_EXPORT void
1779 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1780                 gint page_num, gpointer user_data)
1781 {
1782   state *s = global_state_kludge;  /* I hate C so much... */
1783   pref_changed_cb (GTK_WIDGET (notebook), user_data);
1784
1785   /* If we're switching to page 0, schedule the current hack to be run.
1786      Otherwise, schedule it to stop. */
1787   if (page_num == 0)
1788     populate_demo_window (s, selected_list_element (s));
1789   else
1790     schedule_preview (s, 0);
1791 }
1792
1793 #ifdef HAVE_GTK2
1794 static void
1795 list_activated_cb (GtkTreeView       *list,
1796                    GtkTreePath       *path,
1797                    GtkTreeViewColumn *column,
1798                    gpointer           data)
1799 {
1800   state *s = data;
1801   char *str;
1802   int list_elt;
1803
1804   STFU g_return_if_fail (!gdk_pointer_is_grabbed ());
1805
1806   str = gtk_tree_path_to_string (path);
1807   list_elt = strtol (str, NULL, 10);
1808   g_free (str);
1809
1810   if (list_elt >= 0)
1811     run_hack (s, list_elt, True);
1812 }
1813
1814 static void
1815 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
1816 {
1817   state *s = (state *)data;
1818   GtkTreeModel *model;
1819   GtkTreeIter iter;
1820   GtkTreePath *path;
1821   char *str;
1822   int list_elt;
1823  
1824   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1825     return;
1826
1827   path = gtk_tree_model_get_path (model, &iter);
1828   str = gtk_tree_path_to_string (path);
1829   list_elt = strtol (str, NULL, 10);
1830
1831   gtk_tree_path_free (path);
1832   g_free (str);
1833
1834   populate_demo_window (s, list_elt);
1835   flush_dialog_changes_and_save (s);
1836
1837   /* Re-populate the Settings window any time a new item is selected
1838      in the list, in case both windows are currently visible.
1839    */
1840   populate_popup_window (s);
1841 }
1842
1843 #else /* !HAVE_GTK2 */
1844
1845 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
1846                                               list_select_cb that comes in
1847                                               *after* we've double-clicked.
1848                                             */
1849
1850 static gint
1851 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1852                      gpointer data)
1853 {
1854   state *s = (state *) data;
1855   if (event->type == GDK_2BUTTON_PRESS)
1856     {
1857       GtkList *list = GTK_LIST (name_to_widget (s, "list"));
1858       int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
1859
1860       last_doubleclick_time = time ((time_t *) 0);
1861
1862       if (list_elt >= 0)
1863         run_hack (s, list_elt, True);
1864     }
1865
1866   return FALSE;
1867 }
1868
1869
1870 static void
1871 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
1872 {
1873   state *s = (state *) data;
1874   time_t now = time ((time_t *) 0);
1875
1876   if (now >= last_doubleclick_time + 2)
1877     {
1878       int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
1879       populate_demo_window (s, list_elt);
1880       flush_dialog_changes_and_save (s);
1881     }
1882 }
1883
1884 static void
1885 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
1886 {
1887   state *s = (state *) data;
1888   populate_demo_window (s, -1);
1889   flush_dialog_changes_and_save (s);
1890 }
1891
1892 #endif /* !HAVE_GTK2 */
1893
1894
1895 /* Called when the checkboxes that are in the left column of the
1896    scrolling list are clicked.  This both populates the right pane
1897    (just as clicking on the label (really, listitem) does) and
1898    also syncs this checkbox with  the right pane Enabled checkbox.
1899  */
1900 static void
1901 list_checkbox_cb (
1902 #ifdef HAVE_GTK2
1903                   GtkCellRendererToggle *toggle,
1904                   gchar                 *path_string,
1905 #else  /* !HAVE_GTK2 */
1906                   GtkWidget *cb,
1907 #endif /* !HAVE_GTK2 */
1908                   gpointer               data)
1909 {
1910   state *s = (state *) data;
1911
1912 #ifdef HAVE_GTK2
1913   GtkScrolledWindow *scroller =
1914     GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
1915   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
1916   GtkTreeModel *model = gtk_tree_view_get_model (list);
1917   GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1918   GtkTreeIter iter;
1919   gboolean active;
1920 #else /* !HAVE_GTK2 */
1921   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
1922   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
1923
1924   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
1925   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
1926   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
1927 #endif /* !HAVE_GTK2 */
1928   GtkAdjustment *adj;
1929   double scroll_top;
1930
1931   int list_elt;
1932
1933 #ifdef HAVE_GTK2
1934   if (!gtk_tree_model_get_iter (model, &iter, path))
1935     {
1936       g_warning ("bad path: %s", path_string);
1937       return;
1938     }
1939   gtk_tree_path_free (path);
1940
1941   gtk_tree_model_get (model, &iter,
1942                       COL_ENABLED, &active,
1943                       -1);
1944
1945   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1946                       COL_ENABLED, !active,
1947                       -1);
1948
1949   list_elt = strtol (path_string, NULL, 10);  
1950 #else  /* !HAVE_GTK2 */
1951   list_elt = gtk_list_child_position (list, line);
1952 #endif /* !HAVE_GTK2 */
1953
1954   /* remember previous scroll position of the top of the list */
1955   adj = gtk_scrolled_window_get_vadjustment (scroller);
1956   scroll_top = adj->value;
1957
1958   flush_dialog_changes_and_save (s);
1959   force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
1960   populate_demo_window (s, list_elt);
1961   
1962   /* restore the previous scroll position of the top of the list.
1963      this is weak, but I don't really know why it's moving... */
1964   gtk_adjustment_set_value (adj, scroll_top);
1965 }
1966
1967
1968 typedef struct {
1969   state *state;
1970   GtkFileSelection *widget;
1971 } file_selection_data;
1972
1973
1974
1975 static void
1976 store_image_directory (GtkWidget *button, gpointer user_data)
1977 {
1978   file_selection_data *fsd = (file_selection_data *) user_data;
1979   state *s = fsd->state;
1980   GtkFileSelection *selector = fsd->widget;
1981   GtkWidget *top = s->toplevel_widget;
1982   saver_preferences *p = &s->prefs;
1983   const char *path = gtk_file_selection_get_filename (selector);
1984
1985   if (p->image_directory && !strcmp(p->image_directory, path))
1986     return;  /* no change */
1987
1988   if (!directory_p (path))
1989     {
1990       char b[255];
1991       sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
1992       warning_dialog (GTK_WIDGET (top), b, False, 100);
1993       return;
1994     }
1995
1996   if (p->image_directory) free (p->image_directory);
1997   p->image_directory = normalize_directory (path);
1998
1999   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2000                       (p->image_directory ? p->image_directory : ""));
2001   demo_write_init_file (s, p);
2002 }
2003
2004
2005 static void
2006 store_text_file (GtkWidget *button, gpointer user_data)
2007 {
2008   file_selection_data *fsd = (file_selection_data *) user_data;
2009   state *s = fsd->state;
2010   GtkFileSelection *selector = fsd->widget;
2011   GtkWidget *top = s->toplevel_widget;
2012   saver_preferences *p = &s->prefs;
2013   const char *path = gtk_file_selection_get_filename (selector);
2014
2015   if (p->text_file && !strcmp(p->text_file, path))
2016     return;  /* no change */
2017
2018   if (!file_p (path))
2019     {
2020       char b[255];
2021       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2022       warning_dialog (GTK_WIDGET (top), b, False, 100);
2023       return;
2024     }
2025
2026   if (p->text_file) free (p->text_file);
2027   p->text_file = normalize_directory (path);
2028
2029   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2030                       (p->text_file ? p->text_file : ""));
2031   demo_write_init_file (s, p);
2032 }
2033
2034
2035 static void
2036 store_text_program (GtkWidget *button, gpointer user_data)
2037 {
2038   file_selection_data *fsd = (file_selection_data *) user_data;
2039   state *s = fsd->state;
2040   GtkFileSelection *selector = fsd->widget;
2041   /*GtkWidget *top = s->toplevel_widget;*/
2042   saver_preferences *p = &s->prefs;
2043   const char *path = gtk_file_selection_get_filename (selector);
2044
2045   if (p->text_program && !strcmp(p->text_program, path))
2046     return;  /* no change */
2047
2048 # if 0
2049   if (!file_p (path))
2050     {
2051       char b[255];
2052       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2053       warning_dialog (GTK_WIDGET (top), b, False, 100);
2054       return;
2055     }
2056 # endif
2057
2058   if (p->text_program) free (p->text_program);
2059   p->text_program = normalize_directory (path);
2060
2061   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2062                       (p->text_program ? p->text_program : ""));
2063   demo_write_init_file (s, p);
2064 }
2065
2066
2067
2068 static void
2069 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
2070 {
2071   file_selection_data *fsd = (file_selection_data *) user_data;
2072   gtk_widget_hide (GTK_WIDGET (fsd->widget));
2073 }
2074
2075 static void
2076 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
2077 {
2078   browse_image_dir_cancel (button, user_data);
2079   store_image_directory (button, user_data);
2080 }
2081
2082 static void
2083 browse_text_file_ok (GtkWidget *button, gpointer user_data)
2084 {
2085   browse_image_dir_cancel (button, user_data);
2086   store_text_file (button, user_data);
2087 }
2088
2089 static void
2090 browse_text_program_ok (GtkWidget *button, gpointer user_data)
2091 {
2092   browse_image_dir_cancel (button, user_data);
2093   store_text_program (button, user_data);
2094 }
2095
2096 static void
2097 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
2098 {
2099   browse_image_dir_cancel (widget, user_data);
2100 }
2101
2102
2103 G_MODULE_EXPORT void
2104 browse_image_dir_cb (GtkButton *button, gpointer user_data)
2105 {
2106   state *s = global_state_kludge;  /* I hate C so much... */
2107   saver_preferences *p = &s->prefs;
2108   static file_selection_data *fsd = 0;
2109
2110   GtkFileSelection *selector = GTK_FILE_SELECTION(
2111     gtk_file_selection_new ("Please select the image directory."));
2112
2113   if (!fsd)
2114     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2115
2116   fsd->widget = selector;
2117   fsd->state = s;
2118
2119   if (p->image_directory && *p->image_directory)
2120     gtk_file_selection_set_filename (selector, p->image_directory);
2121
2122   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2123                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
2124                       (gpointer *) fsd);
2125   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2126                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2127                       (gpointer *) fsd);
2128   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2129                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2130                       (gpointer *) fsd);
2131
2132   gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
2133
2134   gtk_window_set_modal (GTK_WINDOW (selector), True);
2135   gtk_widget_show (GTK_WIDGET (selector));
2136 }
2137
2138
2139 G_MODULE_EXPORT void
2140 browse_text_file_cb (GtkButton *button, gpointer user_data)
2141 {
2142   state *s = global_state_kludge;  /* I hate C so much... */
2143   saver_preferences *p = &s->prefs;
2144   static file_selection_data *fsd = 0;
2145
2146   GtkFileSelection *selector = GTK_FILE_SELECTION(
2147     gtk_file_selection_new ("Please select a text file."));
2148
2149   if (!fsd)
2150     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2151
2152   fsd->widget = selector;
2153   fsd->state = s;
2154
2155   if (p->text_file && *p->text_file)
2156     gtk_file_selection_set_filename (selector, p->text_file);
2157
2158   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2159                       "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
2160                       (gpointer *) fsd);
2161   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2162                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2163                       (gpointer *) fsd);
2164   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2165                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2166                       (gpointer *) fsd);
2167
2168   gtk_window_set_modal (GTK_WINDOW (selector), True);
2169   gtk_widget_show (GTK_WIDGET (selector));
2170 }
2171
2172
2173 G_MODULE_EXPORT void
2174 browse_text_program_cb (GtkButton *button, gpointer user_data)
2175 {
2176   state *s = global_state_kludge;  /* I hate C so much... */
2177   saver_preferences *p = &s->prefs;
2178   static file_selection_data *fsd = 0;
2179
2180   GtkFileSelection *selector = GTK_FILE_SELECTION(
2181     gtk_file_selection_new ("Please select a text-generating program."));
2182
2183   if (!fsd)
2184     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2185
2186   fsd->widget = selector;
2187   fsd->state = s;
2188
2189   if (p->text_program && *p->text_program)
2190     gtk_file_selection_set_filename (selector, p->text_program);
2191
2192   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2193                       "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
2194                       (gpointer *) fsd);
2195   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2196                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2197                       (gpointer *) fsd);
2198   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2199                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2200                       (gpointer *) fsd);
2201
2202   gtk_window_set_modal (GTK_WINDOW (selector), True);
2203   gtk_widget_show (GTK_WIDGET (selector));
2204 }
2205
2206
2207
2208
2209
2210 G_MODULE_EXPORT void
2211 settings_cb (GtkButton *button, gpointer user_data)
2212 {
2213   state *s = global_state_kludge;  /* I hate C so much... */
2214   int list_elt = selected_list_element (s);
2215
2216   populate_demo_window (s, list_elt);   /* reset the widget */
2217   populate_popup_window (s);            /* create UI on popup window */
2218   gtk_widget_show (s->popup_widget);
2219 }
2220
2221 static void
2222 settings_sync_cmd_text (state *s)
2223 {
2224 # ifdef HAVE_XML
2225   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2226   char *cmd_line = get_configurator_command_line (s->cdata);
2227   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2228   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2229   free (cmd_line);
2230 # endif /* HAVE_XML */
2231 }
2232
2233 G_MODULE_EXPORT void
2234 settings_adv_cb (GtkButton *button, gpointer user_data)
2235 {
2236   state *s = global_state_kludge;  /* I hate C so much... */
2237   GtkNotebook *notebook =
2238     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2239
2240   settings_sync_cmd_text (s);
2241   gtk_notebook_set_page (notebook, 1);
2242 }
2243
2244 G_MODULE_EXPORT void
2245 settings_std_cb (GtkButton *button, gpointer user_data)
2246 {
2247   state *s = global_state_kludge;  /* I hate C so much... */
2248   GtkNotebook *notebook =
2249     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2250
2251   /* Re-create UI to reflect the in-progress command-line settings. */
2252   populate_popup_window (s);
2253
2254   gtk_notebook_set_page (notebook, 0);
2255 }
2256
2257 G_MODULE_EXPORT void
2258 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
2259                          gint page_num, gpointer user_data)
2260 {
2261   state *s = global_state_kludge;  /* I hate C so much... */
2262   GtkWidget *adv = name_to_widget (s, "adv_button");
2263   GtkWidget *std = name_to_widget (s, "std_button");
2264
2265   if (page_num == 0)
2266     {
2267       gtk_widget_show (adv);
2268       gtk_widget_hide (std);
2269     }
2270   else if (page_num == 1)
2271     {
2272       gtk_widget_hide (adv);
2273       gtk_widget_show (std);
2274     }
2275   else
2276     abort();
2277 }
2278
2279
2280
2281 G_MODULE_EXPORT void
2282 settings_cancel_cb (GtkButton *button, gpointer user_data)
2283 {
2284   state *s = global_state_kludge;  /* I hate C so much... */
2285   gtk_widget_hide (s->popup_widget);
2286 }
2287
2288 G_MODULE_EXPORT void
2289 settings_ok_cb (GtkButton *button, gpointer user_data)
2290 {
2291   state *s = global_state_kludge;  /* I hate C so much... */
2292   GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2293   int page = gtk_notebook_get_current_page (notebook);
2294
2295   if (page == 0)
2296     /* Regenerate the command-line from the widget contents before saving.
2297        But don't do this if we're looking at the command-line page already,
2298        or we will blow away what they typed... */
2299     settings_sync_cmd_text (s);
2300
2301   flush_popup_changes_and_save (s);
2302   gtk_widget_hide (s->popup_widget);
2303 }
2304
2305 static gboolean
2306 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
2307 {
2308   state *s = (state *) data;
2309   settings_cancel_cb (0, (gpointer) s);
2310   return TRUE;
2311 }
2312
2313
2314 \f
2315 /* Populating the various widgets
2316  */
2317
2318
2319 /* Returns the number of the last hack run by the server.
2320  */
2321 static int
2322 server_current_hack (void)
2323 {
2324   Atom type;
2325   int format;
2326   unsigned long nitems, bytesafter;
2327   unsigned char *dataP = 0;
2328   Display *dpy = GDK_DISPLAY();
2329   int hack_number = -1;
2330
2331   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
2332                           XA_SCREENSAVER_STATUS,
2333                           0, 3, False, XA_INTEGER,
2334                           &type, &format, &nitems, &bytesafter,
2335                           &dataP)
2336       == Success
2337       && type == XA_INTEGER
2338       && nitems >= 3
2339       && dataP)
2340     {
2341       CARD32 *data = (CARD32 *) dataP;
2342       hack_number = (int) data[2] - 1;
2343     }
2344
2345   if (dataP) XFree (dataP);
2346
2347   return hack_number;
2348 }
2349
2350
2351 /* Finds the number of the last hack that was run, and makes that item be
2352    selected by default.
2353  */
2354 static void
2355 scroll_to_current_hack (state *s)
2356 {
2357   saver_preferences *p = &s->prefs;
2358   int hack_number = -1;
2359
2360   if (p->mode == ONE_HACK)                 /* in "one" mode, use the one */
2361     hack_number = p->selected_hack;
2362   if (hack_number < 0)                     /* otherwise, use the last-run */
2363     hack_number = server_current_hack ();
2364   if (hack_number < 0)                     /* failing that, last "one mode" */
2365     hack_number = p->selected_hack;
2366   if (hack_number < 0)                     /* failing that, newest hack. */
2367     {
2368       /* We should only get here if the user does not have a .xscreensaver
2369          file, and the screen has not been blanked with a hack since X
2370          started up: in other words, this is probably a fresh install.
2371
2372          Instead of just defaulting to hack #0 (in either "programs" or
2373          "alphabetical" order) let's try to default to the last runnable
2374          hack in the "programs" list: this is probably the hack that was
2375          most recently added to the xscreensaver distribution (and so
2376          it's probably the currently-coolest one!)
2377        */
2378       hack_number = p->screenhacks_count-1;
2379       while (hack_number > 0 &&
2380              ! (s->hacks_available_p[hack_number] &&
2381                 p->screenhacks[hack_number]->enabled_p))
2382         hack_number--;
2383     }
2384
2385   if (hack_number >= 0 && hack_number < p->screenhacks_count)
2386     {
2387       int list_elt = s->hack_number_to_list_elt[hack_number];
2388       GtkWidget *list = name_to_widget (s, "list");
2389       force_list_select_item (s, list, list_elt, True);
2390       populate_demo_window (s, list_elt);
2391     }
2392 }
2393
2394
2395 static void
2396 populate_hack_list (state *s)
2397 {
2398   Display *dpy = GDK_DISPLAY();
2399 #ifdef HAVE_GTK2
2400   saver_preferences *p = &s->prefs;
2401   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2402   GtkListStore *model;
2403   GtkTreeSelection *selection;
2404   GtkCellRenderer *ren;
2405   GtkTreeIter iter;
2406   int i;
2407
2408   g_object_get (G_OBJECT (list),
2409                 "model", &model,
2410                 NULL);
2411   if (!model)
2412     {
2413       model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
2414       g_object_set (G_OBJECT (list), "model", model, NULL);
2415       g_object_unref (model);
2416
2417       ren = gtk_cell_renderer_toggle_new ();
2418       gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
2419                                                    _("Use"), ren,
2420                                                    "active", COL_ENABLED,
2421                                                    NULL);
2422
2423       g_signal_connect (ren, "toggled",
2424                         G_CALLBACK (list_checkbox_cb),
2425                         s);
2426
2427       ren = gtk_cell_renderer_text_new ();
2428       gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
2429                                                    _("Screen Saver"), ren,
2430                                                    "markup", COL_NAME,
2431                                                    NULL);
2432
2433       g_signal_connect_after (list, "row_activated",
2434                               G_CALLBACK (list_activated_cb),
2435                               s);
2436
2437       selection = gtk_tree_view_get_selection (list);
2438       g_signal_connect (selection, "changed",
2439                         G_CALLBACK (list_select_changed_cb),
2440                         s);
2441
2442     }
2443
2444   for (i = 0; i < s->list_count; i++)
2445     {
2446       int hack_number = s->list_elt_to_hack_number[i];
2447       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2448       char *pretty_name;
2449       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2450
2451       if (!hack) continue;
2452
2453       /* If we're to suppress uninstalled hacks, check $PATH now. */
2454       if (p->ignore_uninstalled_p && !available_p)
2455         continue;
2456
2457       pretty_name = (hack->name
2458                      ? strdup (hack->name)
2459                      : make_hack_name (dpy, hack->command));
2460
2461       if (!available_p)
2462         {
2463           /* Make the text foreground be the color of insensitive widgets
2464              (but don't actually make it be insensitive, since we still
2465              want to be able to click on it.)
2466            */
2467           GtkStyle *style = GTK_WIDGET (list)->style;
2468           GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
2469        /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
2470           char *buf = (char *) malloc (strlen (pretty_name) + 100);
2471
2472           sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
2473                       /*     " background=\"#%02X%02X%02X\""  */
2474                         ">%s</span>",
2475                    fg->red >> 8, fg->green >> 8, fg->blue >> 8,
2476                 /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
2477                    pretty_name);
2478           free (pretty_name);
2479           pretty_name = buf;
2480         }
2481
2482       gtk_list_store_append (model, &iter);
2483       gtk_list_store_set (model, &iter,
2484                           COL_ENABLED, hack->enabled_p,
2485                           COL_NAME, pretty_name,
2486                           -1);
2487       free (pretty_name);
2488     }
2489
2490 #else /* !HAVE_GTK2 */
2491
2492   saver_preferences *p = &s->prefs;
2493   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2494   int i;
2495   for (i = 0; i < s->list_count; i++)
2496     {
2497       int hack_number = s->list_elt_to_hack_number[i];
2498       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2499
2500       /* A GtkList must contain only GtkListItems, but those can contain
2501          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
2502          and a Label.  We handle single and double click events on the
2503          line itself, for clicking on the text, but the interior checkbox
2504          also handles its own events.
2505        */
2506       GtkWidget *line;
2507       GtkWidget *line_hbox;
2508       GtkWidget *line_check;
2509       GtkWidget *line_label;
2510       char *pretty_name;
2511       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2512
2513       if (!hack) continue;
2514
2515       /* If we're to suppress uninstalled hacks, check $PATH now. */
2516       if (p->ignore_uninstalled_p && !available_p)
2517         continue;
2518
2519       pretty_name = (hack->name
2520                      ? strdup (hack->name)
2521                      : make_hack_name (hack->command));
2522
2523       line = gtk_list_item_new ();
2524       line_hbox = gtk_hbox_new (FALSE, 0);
2525       line_check = gtk_check_button_new ();
2526       line_label = gtk_label_new (pretty_name);
2527
2528       gtk_container_add (GTK_CONTAINER (line), line_hbox);
2529       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
2530       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
2531
2532       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
2533                                     hack->enabled_p);
2534       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
2535
2536       gtk_widget_show (line_check);
2537       gtk_widget_show (line_label);
2538       gtk_widget_show (line_hbox);
2539       gtk_widget_show (line);
2540
2541       free (pretty_name);
2542
2543       gtk_container_add (GTK_CONTAINER (list), line);
2544       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
2545                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
2546                           (gpointer) s);
2547
2548       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
2549                           GTK_SIGNAL_FUNC (list_checkbox_cb),
2550                           (gpointer) s);
2551
2552       gtk_widget_show (line);
2553
2554       if (!available_p)
2555         {
2556           /* Make the widget be colored like insensitive widgets
2557              (but don't actually make it be insensitive, since we
2558              still want to be able to click on it.)
2559            */
2560           GtkRcStyle *rc_style;
2561           GdkColor fg, bg;
2562
2563           gtk_widget_realize (GTK_WIDGET (line_label));
2564
2565           fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
2566           bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
2567
2568           rc_style = gtk_rc_style_new ();
2569           rc_style->fg[GTK_STATE_NORMAL] = fg;
2570           rc_style->bg[GTK_STATE_NORMAL] = bg;
2571           rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
2572
2573           gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
2574           gtk_rc_style_unref (rc_style);
2575         }
2576     }
2577
2578   gtk_signal_connect (GTK_OBJECT (list), "select_child",
2579                       GTK_SIGNAL_FUNC (list_select_cb),
2580                       (gpointer) s);
2581   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
2582                       GTK_SIGNAL_FUNC (list_unselect_cb),
2583                       (gpointer) s);
2584 #endif /* !HAVE_GTK2 */
2585 }
2586
2587 static void
2588 update_list_sensitivity (state *s)
2589 {
2590   saver_preferences *p = &s->prefs;
2591   Bool sensitive = (p->mode == RANDOM_HACKS ||
2592                     p->mode == RANDOM_HACKS_SAME ||
2593                     p->mode == ONE_HACK);
2594   Bool checkable = (p->mode == RANDOM_HACKS ||
2595                     p->mode == RANDOM_HACKS_SAME);
2596   Bool blankable = (p->mode != DONT_BLANK);
2597
2598 #ifndef HAVE_GTK2
2599   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
2600   GtkWidget *use      = name_to_widget (s, "use_col_frame");
2601 #endif /* HAVE_GTK2 */
2602   GtkWidget *scroller = name_to_widget (s, "scroller");
2603   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
2604   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
2605
2606 #ifdef HAVE_GTK2
2607   GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
2608   GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
2609 #else /* !HAVE_GTK2 */
2610   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2611   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
2612
2613   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
2614 #endif /* !HAVE_GTK2 */
2615   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
2616   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
2617
2618   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
2619
2620 #ifdef HAVE_GTK2
2621   gtk_tree_view_column_set_visible (use, checkable);
2622 #else  /* !HAVE_GTK2 */
2623   if (checkable)
2624     gtk_widget_show (use);   /* the "Use" column header */
2625   else
2626     gtk_widget_hide (use);
2627
2628   while (kids)
2629     {
2630       GtkBin *line = GTK_BIN (kids->data);
2631       GtkContainer *line_hbox = GTK_CONTAINER (line->child);
2632       GtkWidget *line_check =
2633         GTK_WIDGET (gtk_container_children (line_hbox)->data);
2634       
2635       if (checkable)
2636         gtk_widget_show (line_check);
2637       else
2638         gtk_widget_hide (line_check);
2639
2640       kids = kids->next;
2641     }
2642 #endif /* !HAVE_GTK2 */
2643 }
2644
2645
2646 static void
2647 populate_prefs_page (state *s)
2648 {
2649   saver_preferences *p = &s->prefs;
2650
2651   Bool can_lock_p = True;
2652
2653   /* Disable all the "lock" controls if locking support was not provided
2654      at compile-time, or if running on MacOS. */
2655 # if defined(NO_LOCKING) || defined(__APPLE__)
2656   can_lock_p = False;
2657 # endif
2658
2659
2660   /* If there is only one screen, the mode menu contains
2661      "random" but not "random-same".
2662    */
2663   if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
2664     p->mode = RANDOM_HACKS;
2665
2666
2667   /* The file supports timeouts of less than a minute, but the GUI does
2668      not, so throttle the values to be at least one minute (since "0" is
2669      a bad rounding choice...)
2670    */
2671 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2672   THROTTLE (timeout);
2673   THROTTLE (cycle);
2674   /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
2675 # undef THROTTLE
2676
2677 # define FMT_MINUTES(NAME,N) \
2678     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2679
2680 # define FMT_SECONDS(NAME,N) \
2681     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2682
2683   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
2684   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
2685   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
2686   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2687   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2688   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
2689   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
2690
2691 # undef FMT_MINUTES
2692 # undef FMT_SECONDS
2693
2694 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2695   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2696                                 (ACTIVEP))
2697
2698   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
2699 #if 0
2700   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
2701   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
2702   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
2703 #endif
2704   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
2705   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
2706   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2707   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2708   TOGGLE_ACTIVE ("install_button",    p->install_cmap_p);
2709   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
2710   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
2711
2712   switch (p->tmode)
2713     {
2714     case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio",         True); break;
2715     case TEXT_FILE:    TOGGLE_ACTIVE ("text_file_radio",    True); break;
2716     case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
2717     case TEXT_URL:     TOGGLE_ACTIVE ("text_url_radio",     True); break;
2718     default:           TOGGLE_ACTIVE ("text_host_radio",    True); break;
2719     }
2720
2721 # undef TOGGLE_ACTIVE
2722
2723   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2724                       (p->image_directory ? p->image_directory : ""));
2725   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2726                             p->random_image_p);
2727   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2728                             p->random_image_p);
2729
2730   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
2731                       (p->text_literal ? p->text_literal : ""));
2732   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2733                       (p->text_file ? p->text_file : ""));
2734   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2735                       (p->text_program ? p->text_program : ""));
2736   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
2737                       (p->text_url ? p->text_url : ""));
2738
2739   gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
2740                             p->tmode == TEXT_LITERAL);
2741   gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
2742                             p->tmode == TEXT_FILE);
2743   gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
2744                             p->tmode == TEXT_FILE);
2745   gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
2746                             p->tmode == TEXT_PROGRAM);
2747   gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
2748                             p->tmode == TEXT_PROGRAM);
2749   gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
2750                             p->tmode == TEXT_URL);
2751
2752
2753   /* Map the `saver_mode' enum to mode menu to values. */
2754   {
2755     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2756
2757     int i;
2758     for (i = 0; i < countof(mode_menu_order); i++)
2759       if (mode_menu_order[i] == p->mode)
2760         break;
2761     gtk_option_menu_set_history (opt, i);
2762     update_list_sensitivity (s);
2763   }
2764
2765   {
2766     Bool found_any_writable_cells = False;
2767     Bool fading_possible = False;
2768     Bool dpms_supported = False;
2769
2770     Display *dpy = GDK_DISPLAY();
2771     int nscreens = ScreenCount(dpy);  /* real screens, not Xinerama */
2772     int i;
2773     for (i = 0; i < nscreens; i++)
2774       {
2775         Screen *s = ScreenOfDisplay (dpy, i);
2776         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2777           {
2778             found_any_writable_cells = True;
2779             break;
2780           }
2781       }
2782
2783     fading_possible = found_any_writable_cells;
2784 #ifdef HAVE_XF86VMODE_GAMMA
2785     fading_possible = True;
2786 #endif
2787
2788 #ifdef HAVE_DPMS_EXTENSION
2789     {
2790       int op = 0, event = 0, error = 0;
2791       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2792         dpms_supported = True;
2793     }
2794 #endif /* HAVE_DPMS_EXTENSION */
2795
2796
2797 # define SENSITIZE(NAME,SENSITIVEP) \
2798     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2799
2800     /* Blanking and Locking
2801      */
2802     SENSITIZE ("lock_button",     can_lock_p);
2803     SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
2804     SENSITIZE ("lock_mlabel",     can_lock_p && p->lock_p);
2805
2806     /* DPMS
2807      */
2808     SENSITIZE ("dpms_frame",              dpms_supported);
2809     SENSITIZE ("dpms_button",             dpms_supported);
2810     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
2811     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
2812     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2813     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
2814     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
2815     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2816     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
2817     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
2818     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
2819
2820     /* Colormaps
2821      */
2822     SENSITIZE ("cmap_frame",      found_any_writable_cells || fading_possible);
2823     SENSITIZE ("install_button",  found_any_writable_cells);
2824     SENSITIZE ("fade_button",     fading_possible);
2825     SENSITIZE ("unfade_button",   fading_possible);
2826
2827     SENSITIZE ("fade_label",      (fading_possible &&
2828                                    (p->fade_p || p->unfade_p)));
2829     SENSITIZE ("fade_spinbutton", (fading_possible &&
2830                                    (p->fade_p || p->unfade_p)));
2831
2832 # undef SENSITIZE
2833   }
2834 }
2835
2836
2837 static void
2838 populate_popup_window (state *s)
2839 {
2840   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2841   char *doc_string = 0;
2842
2843   /* #### not in Gtk 1.2
2844   gtk_label_set_selectable (doc);
2845    */
2846
2847 # ifdef HAVE_XML
2848   if (s->cdata)
2849     {
2850       free_conf_data (s->cdata);
2851       s->cdata = 0;
2852     }
2853
2854   {
2855     saver_preferences *p = &s->prefs;
2856     int list_elt = selected_list_element (s);
2857     int hack_number = (list_elt >= 0 && list_elt < s->list_count
2858                        ? s->list_elt_to_hack_number[list_elt]
2859                        : -1);
2860     screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2861     if (hack)
2862       {
2863         GtkWidget *parent = name_to_widget (s, "settings_vbox");
2864         GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2865         const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2866         s->cdata = load_configurator (cmd_line, s->debug_p);
2867         if (s->cdata && s->cdata->widget)
2868           gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
2869                               TRUE, TRUE, 0);
2870       }
2871   }
2872
2873   doc_string = (s->cdata
2874                 ? s->cdata->description
2875                 : 0);
2876 # else  /* !HAVE_XML */
2877   doc_string = _("Descriptions not available: no XML support compiled in.");
2878 # endif /* !HAVE_XML */
2879
2880   gtk_label_set_text (doc, (doc_string
2881                             ? _(doc_string)
2882                             : _("No description available.")));
2883 }
2884
2885
2886 static void
2887 sensitize_demo_widgets (state *s, Bool sensitive_p)
2888 {
2889   const char *names[] = { "demo", "settings",
2890                           "cmd_label", "cmd_text", "manual",
2891                           "visual", "visual_combo" };
2892   int i;
2893   for (i = 0; i < countof(names); i++)
2894     {
2895       GtkWidget *w = name_to_widget (s, names[i]);
2896       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2897     }
2898 }
2899
2900
2901 static void
2902 sensitize_menu_items (state *s, Bool force_p)
2903 {
2904   static Bool running_p = False;
2905   static time_t last_checked = 0;
2906   time_t now = time ((time_t *) 0);
2907   const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
2908                           /* "demo" */ };
2909   int i;
2910
2911   if (force_p || now > last_checked + 10)   /* check every 10 seconds */
2912     {
2913       running_p = xscreensaver_running_p (s);
2914       last_checked = time ((time_t *) 0);
2915     }
2916
2917   for (i = 0; i < countof(names); i++)
2918     {
2919       GtkWidget *w = name_to_widget (s, names[i]);
2920       gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
2921     }
2922 }
2923
2924
2925 /* When the File menu is de-posted after a "Restart Daemon" command,
2926    the window underneath doesn't repaint for some reason.  I guess this
2927    is a bug in exposure handling in GTK or GDK.  This works around it.
2928  */
2929 static void
2930 force_dialog_repaint (state *s)
2931 {
2932 #if 1
2933   /* Tell GDK to invalidate and repaint the whole window.
2934    */
2935   GdkWindow *w = s->toplevel_widget->window;
2936   GdkRegion *region = gdk_region_new ();
2937   GdkRectangle rect;
2938   rect.x = rect.y = 0;
2939   rect.width = rect.height = 32767;
2940   gdk_region_union_with_rect (region, &rect);
2941   gdk_window_invalidate_region (w, region, True);
2942   gdk_region_destroy (region);
2943   gdk_window_process_updates (w, True);
2944 #else
2945   /* Force the server to send an exposure event by creating and then
2946      destroying a window as a child of the top level shell.
2947    */
2948   Display *dpy = GDK_DISPLAY();
2949   Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
2950   Window w;
2951   XWindowAttributes xgwa;
2952   XGetWindowAttributes (dpy, parent, &xgwa);
2953   w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
2954   XMapRaised (dpy, w);
2955   XDestroyWindow (dpy, w);
2956   XSync (dpy, False);
2957 #endif
2958 }
2959
2960
2961 /* Even though we've given these text fields a maximum number of characters,
2962    their default size is still about 30 characters wide -- so measure out
2963    a string in their font, and resize them to just fit that.
2964  */
2965 static void
2966 fix_text_entry_sizes (state *s)
2967 {
2968   GtkWidget *w;
2969
2970 # if 0   /* appears no longer necessary with Gtk 1.2.10 */
2971   const char * const spinbuttons[] = {
2972     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
2973     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
2974     "dpms_off_spinbutton",
2975     "-fade_spinbutton" };
2976   int i;
2977   int width = 0;
2978
2979   for (i = 0; i < countof(spinbuttons); i++)
2980     {
2981       const char *n = spinbuttons[i];
2982       int cols = 4;
2983       while (*n == '-') n++, cols--;
2984       w = GTK_WIDGET (name_to_widget (s, n));
2985       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
2986       gtk_widget_set_usize (w, width, -2);
2987     }
2988
2989   /* Now fix the width of the combo box.
2990    */
2991   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
2992   w = GTK_COMBO (w)->entry;
2993   width = gdk_string_width (w->style->font, "PseudoColor___");
2994   gtk_widget_set_usize (w, width, -2);
2995
2996   /* Now fix the width of the file entry text.
2997    */
2998   w = GTK_WIDGET (name_to_widget (s, "image_text"));
2999   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
3000   gtk_widget_set_usize (w, width, -2);
3001
3002   /* Now fix the width of the command line text.
3003    */
3004   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
3005   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
3006   gtk_widget_set_usize (w, width, -2);
3007
3008 # endif /* 0 */
3009
3010   /* Now fix the height of the list widget:
3011      make it default to being around 10 text-lines high instead of 4.
3012    */
3013   w = GTK_WIDGET (name_to_widget (s, "list"));
3014   {
3015     int lines = 10;
3016     int height;
3017     int leading = 3;  /* approximate is ok... */
3018     int border = 2;
3019
3020 #ifdef HAVE_GTK2
3021     PangoFontMetrics *pain =
3022       pango_context_get_metrics (gtk_widget_get_pango_context (w),
3023                                  w->style->font_desc,
3024                                  gtk_get_default_language ());
3025     height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
3026                            pango_font_metrics_get_descent (pain));
3027 #else  /* !HAVE_GTK2 */
3028     height = w->style->font->ascent + w->style->font->descent;
3029 #endif /* !HAVE_GTK2 */
3030
3031     height += leading;
3032     height *= lines;
3033     height += border * 2;
3034     w = GTK_WIDGET (name_to_widget (s, "scroller"));
3035     gtk_widget_set_usize (w, -2, height);
3036   }
3037 }
3038
3039
3040 #ifndef HAVE_GTK2
3041 \f
3042 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
3043  */
3044
3045 static char *up_arrow_xpm[] = {
3046   "15 15 4 1",
3047   "     c None",
3048   "-    c #FFFFFF",
3049   "+    c #D6D6D6",
3050   "@    c #000000",
3051
3052   "       @       ",
3053   "       @       ",
3054   "      -+@      ",
3055   "      -+@      ",
3056   "     -+++@     ",
3057   "     -+++@     ",
3058   "    -+++++@    ",
3059   "    -+++++@    ",
3060   "   -+++++++@   ",
3061   "   -+++++++@   ",
3062   "  -+++++++++@  ",
3063   "  -+++++++++@  ",
3064   " -+++++++++++@ ",
3065   " @@@@@@@@@@@@@ ",
3066   "               ",
3067
3068   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3069      the end of the array (Gtk 1.2.5.) */
3070   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3071   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3072 };
3073
3074 static char *down_arrow_xpm[] = {
3075   "15 15 4 1",
3076   "     c None",
3077   "-    c #FFFFFF",
3078   "+    c #D6D6D6",
3079   "@    c #000000",
3080
3081   "               ",
3082   " ------------- ",
3083   " -+++++++++++@ ",
3084   "  -+++++++++@  ",
3085   "  -+++++++++@  ",
3086   "   -+++++++@   ",
3087   "   -+++++++@   ",
3088   "    -+++++@    ",
3089   "    -+++++@    ",
3090   "     -+++@     ",
3091   "     -+++@     ",
3092   "      -+@      ",
3093   "      -+@      ",
3094   "       @       ",
3095   "       @       ",
3096
3097   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3098      the end of the array (Gtk 1.2.5.) */
3099   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3100   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3101 };
3102
3103 static void
3104 pixmapify_button (state *s, int down_p)
3105 {
3106   GdkPixmap *pixmap;
3107   GdkBitmap *mask;
3108   GtkWidget *pixmapwid;
3109   GtkStyle *style;
3110   GtkWidget *w;
3111
3112   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
3113   style = gtk_widget_get_style (w);
3114   mask = 0;
3115   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
3116                                          &style->bg[GTK_STATE_NORMAL],
3117                                          (down_p
3118                                           ? (gchar **) down_arrow_xpm
3119                                           : (gchar **) up_arrow_xpm));
3120   pixmapwid = gtk_pixmap_new (pixmap, mask);
3121   gtk_widget_show (pixmapwid);
3122   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
3123   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
3124 }
3125
3126 static void
3127 map_next_button_cb (GtkWidget *w, gpointer user_data)
3128 {
3129   state *s = (state *) user_data;
3130   pixmapify_button (s, 1);
3131 }
3132
3133 static void
3134 map_prev_button_cb (GtkWidget *w, gpointer user_data)
3135 {
3136   state *s = (state *) user_data;
3137   pixmapify_button (s, 0);
3138 }
3139 #endif /* !HAVE_GTK2 */
3140
3141 \f
3142 #ifndef HAVE_GTK2
3143 /* Work around a Gtk bug that causes label widgets to wrap text too early.
3144  */
3145
3146 static void
3147 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
3148                                              GtkAllocation *allocation,
3149                                              void *foo)
3150 {
3151   GtkRequisition req;
3152   GtkWidgetAuxInfo *aux_info;
3153
3154   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
3155
3156   aux_info->width = allocation->width;
3157   aux_info->height = -2;
3158   aux_info->x = -1;
3159   aux_info->y = -1;
3160
3161   gtk_widget_size_request (label, &req);
3162 }
3163
3164 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
3165  */
3166 static void
3167 eschew_gtk_lossage (GtkLabel *label)
3168 {
3169   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
3170   aux_info->width = GTK_WIDGET (label)->allocation.width;
3171   aux_info->height = -2;
3172   aux_info->x = -1;
3173   aux_info->y = -1;
3174
3175   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
3176
3177   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
3178                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
3179                       0);
3180
3181   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
3182
3183   gtk_widget_queue_resize (GTK_WIDGET (label));
3184 }
3185 #endif /* !HAVE_GTK2 */
3186
3187
3188 static void
3189 populate_demo_window (state *s, int list_elt)
3190 {
3191   Display *dpy = GDK_DISPLAY();
3192   saver_preferences *p = &s->prefs;
3193   screenhack *hack;
3194   char *pretty_name;
3195   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
3196   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "doc_frame"));
3197   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
3198   GtkCombo *vis    = GTK_COMBO (name_to_widget (s, "visual_combo"));
3199   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
3200
3201   if (p->mode == BLANK_ONLY)
3202     {
3203       hack = 0;
3204       pretty_name = strdup (_("Blank Screen"));
3205       schedule_preview (s, 0);
3206     }
3207   else if (p->mode == DONT_BLANK)
3208     {
3209       hack = 0;
3210       pretty_name = strdup (_("Screen Saver Disabled"));
3211       schedule_preview (s, 0);
3212     }
3213   else
3214     {
3215       int hack_number = (list_elt >= 0 && list_elt < s->list_count
3216                          ? s->list_elt_to_hack_number[list_elt]
3217                          : -1);
3218       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
3219
3220       pretty_name = (hack
3221                      ? (hack->name
3222                         ? strdup (hack->name)
3223                         : make_hack_name (dpy, hack->command))
3224                      : 0);
3225
3226       if (hack)
3227         schedule_preview (s, hack->command);
3228       else
3229         schedule_preview (s, 0);
3230     }
3231
3232   if (!pretty_name)
3233     pretty_name = strdup (_("Preview"));
3234
3235   gtk_frame_set_label (frame1, _(pretty_name));
3236   gtk_frame_set_label (frame2, _(pretty_name));
3237
3238   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
3239   gtk_entry_set_position (cmd, 0);
3240
3241   {
3242     char title[255];
3243     sprintf (title, _("%s: %.100s Settings"),
3244              progclass, (pretty_name ? pretty_name : "???"));
3245     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
3246   }
3247
3248   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
3249                       (hack
3250                        ? (hack->visual && *hack->visual
3251                           ? hack->visual
3252                           : _("Any"))
3253                        : ""));
3254
3255   sensitize_demo_widgets (s, (hack ? True : False));
3256
3257   if (pretty_name) free (pretty_name);
3258
3259   ensure_selected_item_visible (list);
3260
3261   s->_selected_list_element = list_elt;
3262 }
3263
3264
3265 static void
3266 widget_deleter (GtkWidget *widget, gpointer data)
3267 {
3268   /* #### Well, I want to destroy these widgets, but if I do that, they get
3269      referenced again, and eventually I get a SEGV.  So instead of
3270      destroying them, I'll just hide them, and leak a bunch of memory
3271      every time the disk file changes.  Go go go Gtk!
3272
3273      #### Ok, that's a lie, I get a crash even if I just hide the widget
3274      and don't ever delete it.  Fuck!
3275    */
3276 #if 0
3277   gtk_widget_destroy (widget);
3278 #else
3279   gtk_widget_hide (widget);
3280 #endif
3281 }
3282
3283
3284 static char **sort_hack_cmp_names_kludge;
3285 static int
3286 sort_hack_cmp (const void *a, const void *b)
3287 {
3288   if (a == b)
3289     return 0;
3290   else
3291     {
3292       int aa = *(int *) a;
3293       int bb = *(int *) b;
3294       const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
3295       return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
3296                      (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
3297     }
3298 }
3299
3300
3301 static void
3302 initialize_sort_map (state *s)
3303 {
3304   Display *dpy = GDK_DISPLAY();
3305   saver_preferences *p = &s->prefs;
3306   int i, j;
3307
3308   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
3309   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
3310   if (s->hacks_available_p) free (s->hacks_available_p);
3311
3312   s->list_elt_to_hack_number = (int *)
3313     calloc (sizeof(int), p->screenhacks_count + 1);
3314   s->hack_number_to_list_elt = (int *)
3315     calloc (sizeof(int), p->screenhacks_count + 1);
3316   s->hacks_available_p = (Bool *)
3317     calloc (sizeof(Bool), p->screenhacks_count + 1);
3318   s->total_available = 0;
3319
3320   /* Check which hacks actually exist on $PATH
3321    */
3322   for (i = 0; i < p->screenhacks_count; i++)
3323     {
3324       screenhack *hack = p->screenhacks[i];
3325       int on = on_path_p (hack->command) ? 1 : 0;
3326       s->hacks_available_p[i] = on;
3327       s->total_available += on;
3328     }
3329
3330   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
3331      hacks, if desired.
3332    */
3333   j = 0;
3334   for (i = 0; i < p->screenhacks_count; i++)
3335     {
3336       if (!p->ignore_uninstalled_p ||
3337           s->hacks_available_p[i])
3338         s->list_elt_to_hack_number[j++] = i;
3339     }
3340   s->list_count = j;
3341
3342   for (; j < p->screenhacks_count; j++)
3343     s->list_elt_to_hack_number[j] = -1;
3344
3345
3346   /* Generate list of sortable names (once)
3347    */
3348   sort_hack_cmp_names_kludge = (char **)
3349     calloc (sizeof(char *), p->screenhacks_count);
3350   for (i = 0; i < p->screenhacks_count; i++)
3351     {
3352       screenhack *hack = p->screenhacks[i];
3353       char *name = (hack->name && *hack->name
3354                     ? strdup (hack->name)
3355                     : make_hack_name (dpy, hack->command));
3356       char *str;
3357       for (str = name; *str; str++)
3358         *str = tolower(*str);
3359       sort_hack_cmp_names_kludge[i] = name;
3360     }
3361
3362   /* Sort list->hack map alphabetically
3363    */
3364   qsort (s->list_elt_to_hack_number,
3365          p->screenhacks_count,
3366          sizeof(*s->list_elt_to_hack_number),
3367          sort_hack_cmp);
3368
3369   /* Free names
3370    */
3371   for (i = 0; i < p->screenhacks_count; i++)
3372     free (sort_hack_cmp_names_kludge[i]);
3373   free (sort_hack_cmp_names_kludge);
3374   sort_hack_cmp_names_kludge = 0;
3375
3376   /* Build inverse table */
3377   for (i = 0; i < p->screenhacks_count; i++)
3378     {
3379       int n = s->list_elt_to_hack_number[i];
3380       if (n != -1)
3381         s->hack_number_to_list_elt[n] = i;
3382     }
3383 }
3384
3385
3386 static int
3387 maybe_reload_init_file (state *s)
3388 {
3389   Display *dpy = GDK_DISPLAY();
3390   saver_preferences *p = &s->prefs;
3391   int status = 0;
3392
3393   static Bool reentrant_lock = False;
3394   if (reentrant_lock) return 0;
3395   reentrant_lock = True;
3396
3397   if (init_file_changed_p (p))
3398     {
3399       const char *f = init_file_name();
3400       char *b;
3401       int list_elt;
3402       GtkWidget *list;
3403
3404       if (!f || !*f) return 0;
3405       b = (char *) malloc (strlen(f) + 1024);
3406       sprintf (b,
3407                _("Warning:\n\n"
3408                  "file \"%s\" has changed, reloading.\n"),
3409                f);
3410       warning_dialog (s->toplevel_widget, b, False, 100);
3411       free (b);
3412
3413       load_init_file (dpy, p);
3414       initialize_sort_map (s);
3415
3416       list_elt = selected_list_element (s);
3417       list = name_to_widget (s, "list");
3418       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
3419       populate_hack_list (s);
3420       force_list_select_item (s, list, list_elt, True);
3421       populate_prefs_page (s);
3422       populate_demo_window (s, list_elt);
3423       ensure_selected_item_visible (list);
3424
3425       status = 1;
3426     }
3427
3428   reentrant_lock = False;
3429   return status;
3430 }
3431
3432
3433 \f
3434 /* Making the preview window have the right X visual (so that GL works.)
3435  */
3436
3437 static Visual *get_best_gl_visual (state *);
3438
3439 static GdkVisual *
3440 x_visual_to_gdk_visual (Visual *xv)
3441 {
3442   GList *gvs = gdk_list_visuals();
3443   if (!xv) return gdk_visual_get_system();
3444   for (; gvs; gvs = gvs->next)
3445     {
3446       GdkVisual *gv = (GdkVisual *) gvs->data;
3447       if (xv == GDK_VISUAL_XVISUAL (gv))
3448         return gv;
3449     }
3450   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
3451            blurb(), (unsigned long) xv->visualid);
3452   abort();
3453 }
3454
3455 static void
3456 clear_preview_window (state *s)
3457 {
3458   GtkWidget *p;
3459   GdkWindow *window;
3460
3461   if (!s->toplevel_widget) return;  /* very early */
3462   p = name_to_widget (s, "preview");
3463   window = p->window;
3464
3465   if (!window) return;
3466
3467   /* Flush the widget background down into the window, in case a subproc
3468      has changed it. */
3469   gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]);
3470   gdk_window_clear (window);
3471
3472   {
3473     int list_elt = selected_list_element (s);
3474     int hack_number = (list_elt >= 0
3475                        ? s->list_elt_to_hack_number[list_elt]
3476                        : -1);
3477     Bool available_p = (hack_number >= 0
3478                         ? s->hacks_available_p [hack_number]
3479                         : True);
3480     Bool nothing_p = (s->total_available < 5);
3481
3482 #ifdef HAVE_GTK2
3483     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
3484     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
3485                            (s->running_preview_error_p
3486                             ? (available_p ? 1 :
3487                                nothing_p ? 3 : 2)
3488                             : 0));
3489 #else /* !HAVE_GTK2 */
3490     if (s->running_preview_error_p)
3491       {
3492         const char * const lines1[] = { N_("No Preview"), N_("Available") };
3493         const char * const lines2[] = { N_("Not"), N_("Installed") };
3494         int nlines = countof(lines1);
3495         int lh = p->style->font->ascent + p->style->font->descent;
3496         int y, i;
3497         gint w, h;
3498
3499         const char * const *lines = (available_p ? lines1 : lines2);
3500
3501         gdk_window_get_size (window, &w, &h);
3502         y = (h - (lh * nlines)) / 2;
3503         y += p->style->font->ascent;
3504         for (i = 0; i < nlines; i++)
3505           {
3506             int sw = gdk_string_width (p->style->font, _(lines[i]));
3507             int x = (w - sw) / 2;
3508             gdk_draw_string (window, p->style->font,
3509                              p->style->fg_gc[GTK_STATE_NORMAL],
3510                              x, y, _(lines[i]));
3511             y += lh;
3512           }
3513       }
3514 #endif /* !HAVE_GTK2 */
3515   }
3516
3517   gdk_flush ();
3518 }
3519
3520
3521 static void
3522 reset_preview_window (state *s)
3523 {
3524   /* On some systems (most recently, MacOS X) OpenGL programs get confused
3525      when you kill one and re-start another on the same window.  So maybe
3526      it's best to just always destroy and recreate the preview window
3527      when changing hacks, instead of always trying to reuse the same one?
3528    */
3529   GtkWidget *pr = name_to_widget (s, "preview");
3530   if (GTK_WIDGET_REALIZED (pr))
3531     {
3532       Window oid = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
3533       Window id;
3534       gtk_widget_hide (pr);
3535       gtk_widget_unrealize (pr);
3536       gtk_widget_realize (pr);
3537       gtk_widget_show (pr);
3538       id = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
3539       if (s->debug_p)
3540         fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
3541                  (unsigned int) oid,
3542                  (unsigned int) id);
3543     }
3544 }
3545
3546
3547 static void
3548 fix_preview_visual (state *s)
3549 {
3550   GtkWidget *widget = name_to_widget (s, "preview");
3551   Visual *xvisual = get_best_gl_visual (s);
3552   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
3553   GdkVisual *dvisual = gdk_visual_get_system();
3554   GdkColormap *cmap = (visual == dvisual
3555                        ? gdk_colormap_get_system ()
3556                        : gdk_colormap_new (visual, False));
3557
3558   if (s->debug_p)
3559     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
3560              (visual == dvisual ? "default" : "non-default"),
3561              (xvisual ? (unsigned long) xvisual->visualid : 0L));
3562
3563   if (!GTK_WIDGET_REALIZED (widget) ||
3564       gtk_widget_get_visual (widget) != visual)
3565     {
3566       gtk_widget_unrealize (widget);
3567       gtk_widget_set_visual (widget, visual);
3568       gtk_widget_set_colormap (widget, cmap);
3569       gtk_widget_realize (widget);
3570     }
3571
3572   /* Set the Widget colors to be white-on-black. */
3573   {
3574     GdkWindow *window = widget->window;
3575     GtkStyle *style = gtk_style_copy (widget->style);
3576     GdkColormap *cmap = gtk_widget_get_colormap (widget);
3577     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
3578     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
3579     GdkGC *fgc = gdk_gc_new(window);
3580     GdkGC *bgc = gdk_gc_new(window);
3581     if (!gdk_color_white (cmap, fg)) abort();
3582     if (!gdk_color_black (cmap, bg)) abort();
3583     gdk_gc_set_foreground (fgc, fg);
3584     gdk_gc_set_background (fgc, bg);
3585     gdk_gc_set_foreground (bgc, bg);
3586     gdk_gc_set_background (bgc, fg);
3587     style->fg_gc[GTK_STATE_NORMAL] = fgc;
3588     style->bg_gc[GTK_STATE_NORMAL] = fgc;
3589     gtk_widget_set_style (widget, style);
3590
3591     /* For debugging purposes, put a title on the window (so that
3592        it can be easily found in the output of "xwininfo -tree".)
3593      */
3594     gdk_window_set_title (window, "Preview");
3595   }
3596
3597   gtk_widget_show (widget);
3598 }
3599
3600 \f
3601 /* Subprocesses
3602  */
3603
3604 static char *
3605 subproc_pretty_name (state *s)
3606 {
3607   if (s->running_preview_cmd)
3608     {
3609       char *ps = strdup (s->running_preview_cmd);
3610       char *ss = strchr (ps, ' ');
3611       if (ss) *ss = 0;
3612       ss = strrchr (ps, '/');
3613       if (!ss)
3614         ss = ps;
3615       else
3616         {
3617           ss = strdup (ss+1);
3618           free (ps);
3619         }
3620       return ss;
3621     }
3622   else
3623     return strdup ("???");
3624 }
3625
3626
3627 static void
3628 reap_zombies (state *s)
3629 {
3630   int wait_status = 0;
3631   pid_t pid;
3632   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
3633     {
3634       if (s->debug_p)
3635         {
3636           if (pid == s->running_preview_pid)
3637             {
3638               char *ss = subproc_pretty_name (s);
3639               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
3640                        (unsigned long) pid, ss);
3641               free (ss);
3642             }
3643           else
3644             fprintf (stderr, "%s: pid %lu died\n", blurb(),
3645                      (unsigned long) pid);
3646         }
3647     }
3648 }
3649
3650
3651 /* Mostly lifted from driver/subprocs.c */
3652 static Visual *
3653 get_best_gl_visual (state *s)
3654 {
3655   Display *dpy = GDK_DISPLAY();
3656   pid_t forked;
3657   int fds [2];
3658   int in, out;
3659   char buf[1024];
3660
3661   char *av[10];
3662   int ac = 0;
3663
3664   av[ac++] = "xscreensaver-gl-helper";
3665   av[ac] = 0;
3666
3667   if (pipe (fds))
3668     {
3669       perror ("error creating pipe:");
3670       return 0;
3671     }
3672
3673   in = fds [0];
3674   out = fds [1];
3675
3676   switch ((int) (forked = fork ()))
3677     {
3678     case -1:
3679       {
3680         sprintf (buf, "%s: couldn't fork", blurb());
3681         perror (buf);
3682         exit (1);
3683       }
3684     case 0:
3685       {
3686         int stdout_fd = 1;
3687
3688         close (in);  /* don't need this one */
3689         close (ConnectionNumber (dpy));         /* close display fd */
3690
3691         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
3692           {
3693             perror ("could not dup() a new stdout:");
3694             return 0;
3695           }
3696
3697         execvp (av[0], av);                     /* shouldn't return. */
3698
3699         if (errno != ENOENT)
3700           {
3701             /* Ignore "no such file or directory" errors, unless verbose.
3702                Issue all other exec errors, though. */
3703             sprintf (buf, "%s: running %s", blurb(), av[0]);
3704             perror (buf);
3705           }
3706
3707         /* Note that one must use _exit() instead of exit() in procs forked
3708            off of Gtk programs -- Gtk installs an atexit handler that has a
3709            copy of the X connection (which we've already closed, for safety.)
3710            If one uses exit() instead of _exit(), then one sometimes gets a
3711            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3712         */
3713         _exit (1);                              /* exits fork */
3714         break;
3715       }
3716     default:
3717       {
3718         int result = 0;
3719         int wait_status = 0;
3720
3721         FILE *f = fdopen (in, "r");
3722         unsigned int v = 0;
3723         char c;
3724
3725         close (out);  /* don't need this one */
3726
3727         *buf = 0;
3728         if (!fgets (buf, sizeof(buf)-1, f))
3729           *buf = 0;
3730         fclose (f);
3731
3732         /* Wait for the child to die. */
3733         waitpid (-1, &wait_status, 0);
3734
3735         if (1 == sscanf (buf, "0x%x %c", &v, &c))
3736           result = (int) v;
3737
3738         if (result == 0)
3739           {
3740             if (s->debug_p)
3741               fprintf (stderr, "%s: %s did not report a GL visual!\n",
3742                        blurb(), av[0]);
3743             return 0;
3744           }
3745         else
3746           {
3747             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3748             if (s->debug_p)
3749               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3750                        blurb(), av[0], result);
3751             if (!v) abort();
3752             return v;
3753           }
3754       }
3755     }
3756
3757   abort();
3758 }
3759
3760
3761 static void
3762 kill_preview_subproc (state *s, Bool reset_p)
3763 {
3764   s->running_preview_error_p = False;
3765
3766   reap_zombies (s);
3767   clear_preview_window (s);
3768
3769   if (s->subproc_check_timer_id)
3770     {
3771       gtk_timeout_remove (s->subproc_check_timer_id);
3772       s->subproc_check_timer_id = 0;
3773       s->subproc_check_countdown = 0;
3774     }
3775
3776   if (s->running_preview_pid)
3777     {
3778       int status = kill (s->running_preview_pid, SIGTERM);
3779       char *ss = subproc_pretty_name (s);
3780
3781       if (status < 0)
3782         {
3783           if (errno == ESRCH)
3784             {
3785               if (s->debug_p)
3786                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3787                          blurb(), (unsigned long) s->running_preview_pid, ss);
3788             }
3789           else
3790             {
3791               char buf [1024];
3792               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3793                        blurb(), (unsigned long) s->running_preview_pid, ss);
3794               perror (buf);
3795             }
3796         }
3797       else {
3798         int endstatus;
3799         waitpid(s->running_preview_pid, &endstatus, 0);
3800         if (s->debug_p)
3801           fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3802                    (unsigned long) s->running_preview_pid, ss);
3803       }
3804
3805       free (ss);
3806       s->running_preview_pid = 0;
3807       if (s->running_preview_cmd) free (s->running_preview_cmd);
3808       s->running_preview_cmd = 0;
3809     }
3810
3811   reap_zombies (s);
3812
3813   if (reset_p)
3814     {
3815       reset_preview_window (s);
3816       clear_preview_window (s);
3817     }
3818 }
3819
3820
3821 /* Immediately and unconditionally launches the given process,
3822    after appending the -window-id option; sets running_preview_pid.
3823  */
3824 static void
3825 launch_preview_subproc (state *s)
3826 {
3827   saver_preferences *p = &s->prefs;
3828   Window id;
3829   char *new_cmd = 0;
3830   pid_t forked;
3831   const char *cmd = s->desired_preview_cmd;
3832
3833   GtkWidget *pr = name_to_widget (s, "preview");
3834   GdkWindow *window;
3835
3836   reset_preview_window (s);
3837
3838   window = pr->window;
3839
3840   s->running_preview_error_p = False;
3841
3842   if (s->preview_suppressed_p)
3843     {
3844       kill_preview_subproc (s, False);
3845       goto DONE;
3846     }
3847
3848   new_cmd = malloc (strlen (cmd) + 40);
3849
3850   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3851   if (id == 0)
3852     {
3853       /* No window id?  No command to run. */
3854       free (new_cmd);
3855       new_cmd = 0;
3856     }
3857   else
3858     {
3859       strcpy (new_cmd, cmd);
3860       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
3861                (unsigned int) id);
3862     }
3863
3864   kill_preview_subproc (s, False);
3865   if (! new_cmd)
3866     {
3867       s->running_preview_error_p = True;
3868       clear_preview_window (s);
3869       goto DONE;
3870     }
3871
3872   switch ((int) (forked = fork ()))
3873     {
3874     case -1:
3875       {
3876         char buf[255];
3877         sprintf (buf, "%s: couldn't fork", blurb());
3878         perror (buf);
3879         s->running_preview_error_p = True;
3880         goto DONE;
3881         break;
3882       }
3883     case 0:
3884       {
3885         close (ConnectionNumber (GDK_DISPLAY()));
3886
3887         hack_subproc_environment (id, s->debug_p);
3888
3889         usleep (250000);  /* pause for 1/4th second before launching, to give
3890                              the previous program time to die and flush its X
3891                              buffer, so we don't get leftover turds on the
3892                              window. */
3893
3894         exec_command (p->shell, new_cmd, p->nice_inferior);
3895         /* Don't bother printing an error message when we are unable to
3896            exec subprocesses; we handle that by polling the pid later.
3897
3898            Note that one must use _exit() instead of exit() in procs forked
3899            off of Gtk programs -- Gtk installs an atexit handler that has a
3900            copy of the X connection (which we've already closed, for safety.)
3901            If one uses exit() instead of _exit(), then one sometimes gets a
3902            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3903         */
3904         _exit (1);  /* exits child fork */
3905         break;
3906
3907       default:
3908
3909         if (s->running_preview_cmd) free (s->running_preview_cmd);
3910         s->running_preview_cmd = strdup (s->desired_preview_cmd);
3911         s->running_preview_pid = forked;
3912
3913         if (s->debug_p)
3914           {
3915             char *ss = subproc_pretty_name (s);
3916             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
3917                      (unsigned long) forked, ss);
3918             free (ss);
3919           }
3920         break;
3921       }
3922     }
3923
3924   schedule_preview_check (s);
3925
3926  DONE:
3927   if (new_cmd) free (new_cmd);
3928   new_cmd = 0;
3929 }
3930
3931
3932 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
3933  */
3934 static void
3935 hack_environment (state *s)
3936 {
3937   static const char *def_path =
3938 # ifdef DEFAULT_PATH_PREFIX
3939     DEFAULT_PATH_PREFIX;
3940 # else
3941     "";
3942 # endif
3943
3944   Display *dpy = GDK_DISPLAY();
3945   const char *odpy = DisplayString (dpy);
3946   char *ndpy = (char *) malloc(strlen(odpy) + 20);
3947   strcpy (ndpy, "DISPLAY=");
3948   strcat (ndpy, odpy);
3949   if (putenv (ndpy))
3950     abort ();
3951
3952   if (s->debug_p)
3953     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
3954
3955   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
3956      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
3957      So we must leak it (and/or the previous setting).  Yay.
3958    */
3959
3960   if (def_path && *def_path)
3961     {
3962       const char *opath = getenv("PATH");
3963       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
3964       strcpy (npath, "PATH=");
3965       strcat (npath, def_path);
3966       strcat (npath, ":");
3967       strcat (npath, opath);
3968
3969       if (putenv (npath))
3970         abort ();
3971       /* do not free(npath) -- see above */
3972
3973       if (s->debug_p)
3974         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
3975     }
3976 }
3977
3978
3979 static void
3980 hack_subproc_environment (Window preview_window_id, Bool debug_p)
3981 {
3982   /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
3983      necessary yet, but it will make programs work if we had invoked
3984      them with "-root" and not with "-window-id" -- which, of course,
3985      doesn't happen.
3986    */
3987   char *nssw = (char *) malloc (40);
3988   sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
3989
3990   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
3991      any more, right?  It's not Posix, but everyone seems to have it. */
3992   if (putenv (nssw))
3993     abort ();
3994
3995   if (debug_p)
3996     fprintf (stderr, "%s: %s\n", blurb(), nssw);
3997
3998   /* do not free(nssw) -- see above */
3999 }
4000
4001
4002 /* Called from a timer:
4003    Launches the currently-chosen subprocess, if it's not already running.
4004    If there's a different process running, kills it.
4005  */
4006 static int
4007 update_subproc_timer (gpointer data)
4008 {
4009   state *s = (state *) data;
4010   if (! s->desired_preview_cmd)
4011     kill_preview_subproc (s, True);
4012   else if (!s->running_preview_cmd ||
4013            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
4014     launch_preview_subproc (s);
4015
4016   s->subproc_timer_id = 0;
4017   return FALSE;  /* do not re-execute timer */
4018 }
4019
4020
4021 /* Call this when you think you might want a preview process running.
4022    It will set a timer that will actually launch that program a second
4023    from now, if you haven't changed your mind (to avoid double-click
4024    spazzing, etc.)  `cmd' may be null meaning "no process".
4025  */
4026 static void
4027 schedule_preview (state *s, const char *cmd)
4028 {
4029   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
4030
4031   if (s->debug_p)
4032     {
4033       if (cmd)
4034         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
4035       else
4036         fprintf (stderr, "%s: scheduling preview death\n", blurb());
4037     }
4038
4039   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
4040   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
4041
4042   if (s->subproc_timer_id)
4043     gtk_timeout_remove (s->subproc_timer_id);
4044   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
4045 }
4046
4047
4048 /* Called from a timer:
4049    Checks to see if the subproc that should be running, actually is.
4050  */
4051 static int
4052 check_subproc_timer (gpointer data)
4053 {
4054   state *s = (state *) data;
4055   Bool again_p = True;
4056
4057   if (s->running_preview_error_p ||   /* already dead */
4058       s->running_preview_pid <= 0)
4059     {
4060       again_p = False;
4061     }
4062   else
4063     {
4064       int status;
4065       reap_zombies (s);
4066       status = kill (s->running_preview_pid, 0);
4067       if (status < 0 && errno == ESRCH)
4068         s->running_preview_error_p = True;
4069
4070       if (s->debug_p)
4071         {
4072           char *ss = subproc_pretty_name (s);
4073           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
4074                    (unsigned long) s->running_preview_pid, ss,
4075                    (s->running_preview_error_p ? "dead" : "alive"));
4076           free (ss);
4077         }
4078
4079       if (s->running_preview_error_p)
4080         {
4081           clear_preview_window (s);
4082           again_p = False;
4083         }
4084     }
4085
4086   /* Otherwise, it's currently alive.  We might be checking again, or we
4087      might be satisfied. */
4088
4089   if (--s->subproc_check_countdown <= 0)
4090     again_p = False;
4091
4092   if (again_p)
4093     return TRUE;     /* re-execute timer */
4094   else
4095     {
4096       s->subproc_check_timer_id = 0;
4097       s->subproc_check_countdown = 0;
4098       return FALSE;  /* do not re-execute timer */
4099     }
4100 }
4101
4102
4103 /* Call this just after launching a subprocess.
4104    This sets a timer that will, five times a second for two seconds,
4105    check whether the program is still running.  The assumption here
4106    is that if the process didn't stay up for more than a couple of
4107    seconds, then either the program doesn't exist, or it doesn't
4108    take a -window-id argument.
4109  */
4110 static void
4111 schedule_preview_check (state *s)
4112 {
4113   int seconds = 2;
4114   int ticks = 5;
4115
4116   if (s->debug_p)
4117     fprintf (stderr, "%s: scheduling check\n", blurb());
4118
4119   if (s->subproc_check_timer_id)
4120     gtk_timeout_remove (s->subproc_check_timer_id);
4121   s->subproc_check_timer_id =
4122     gtk_timeout_add (1000 / ticks,
4123                      check_subproc_timer, (gpointer) s);
4124   s->subproc_check_countdown = ticks * seconds;
4125 }
4126
4127
4128 static Bool
4129 screen_blanked_p (void)
4130 {
4131   Atom type;
4132   int format;
4133   unsigned long nitems, bytesafter;
4134   unsigned char *dataP = 0;
4135   Display *dpy = GDK_DISPLAY();
4136   Bool blanked_p = False;
4137
4138   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
4139                           XA_SCREENSAVER_STATUS,
4140                           0, 3, False, XA_INTEGER,
4141                           &type, &format, &nitems, &bytesafter,
4142                           &dataP)
4143       == Success
4144       && type == XA_INTEGER
4145       && nitems >= 3
4146       && dataP)
4147     {
4148       Atom *data = (Atom *) dataP;
4149       blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
4150     }
4151
4152   if (dataP) XFree (dataP);
4153
4154   return blanked_p;
4155 }
4156
4157 /* Wake up every now and then and see if the screen is blanked.
4158    If it is, kill off the small-window demo -- no point in wasting
4159    cycles by running two screensavers at once...
4160  */
4161 static int
4162 check_blanked_timer (gpointer data)
4163 {
4164   state *s = (state *) data;
4165   Bool blanked_p = screen_blanked_p ();
4166   if (blanked_p && s->running_preview_pid)
4167     {
4168       if (s->debug_p)
4169         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
4170       kill_preview_subproc (s, True);
4171     }
4172
4173   return True;  /* re-execute timer */
4174 }
4175
4176
4177 /* How many screens are there (including Xinerama.)
4178  */
4179 static int
4180 screen_count (Display *dpy)
4181 {
4182   int nscreens = ScreenCount(dpy);
4183 # ifdef HAVE_XINERAMA
4184   if (nscreens <= 1)
4185     {
4186       int event_number, error_number;
4187       if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
4188           XineramaIsActive (dpy))
4189         {
4190           XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
4191           if (xsi) XFree (xsi);
4192         }
4193     }
4194 # endif /* HAVE_XINERAMA */
4195
4196   return nscreens;
4197 }
4198
4199 \f
4200 /* Setting window manager icon
4201  */
4202
4203 static void
4204 init_icon (GdkWindow *window)
4205 {
4206   GdkBitmap *mask = 0;
4207   GdkColor transp;
4208   GdkPixmap *pixmap =
4209     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
4210                                   (gchar **) logo_50_xpm);
4211   if (pixmap)
4212     gdk_window_set_icon (window, 0, pixmap, mask);
4213 }
4214
4215 \f
4216 /* The main demo-mode command loop.
4217  */
4218
4219 #if 0
4220 static Bool
4221 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
4222         XrmRepresentation *type, XrmValue *value, XPointer closure)
4223 {
4224   int i;
4225   for (i = 0; quarks[i]; i++)
4226     {
4227       if (bindings[i] == XrmBindTightly)
4228         fprintf (stderr, (i == 0 ? "" : "."));
4229       else if (bindings[i] == XrmBindLoosely)
4230         fprintf (stderr, "*");
4231       else
4232         fprintf (stderr, " ??? ");
4233       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
4234     }
4235
4236   fprintf (stderr, ": %s\n", (char *) value->addr);
4237
4238   return False;
4239 }
4240 #endif
4241
4242
4243 static void
4244 the_network_is_not_the_computer (state *s)
4245 {
4246   Display *dpy = GDK_DISPLAY();
4247   char *rversion = 0, *ruser = 0, *rhost = 0;
4248   char *luser, *lhost;
4249   char *msg = 0;
4250   struct passwd *p = getpwuid (getuid ());
4251   const char *d = DisplayString (dpy);
4252
4253 # if defined(HAVE_UNAME)
4254   struct utsname uts;
4255   if (uname (&uts) < 0)
4256     lhost = "<UNKNOWN>";
4257   else
4258     lhost = uts.nodename;
4259 # elif defined(VMS)
4260   strcpy (lhost, getenv("SYS$NODE"));
4261 # else  /* !HAVE_UNAME && !VMS */
4262   strcat (lhost, "<UNKNOWN>");
4263 # endif /* !HAVE_UNAME && !VMS */
4264
4265   if (p && p->pw_name)
4266     luser = p->pw_name;
4267   else
4268     luser = "???";
4269
4270   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
4271
4272   /* Make a buffer that's big enough for a number of copies of all the
4273      strings, plus some. */
4274   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
4275                                (ruser ? strlen(ruser) : 0) +
4276                                (rhost ? strlen(rhost) : 0) +
4277                                strlen(lhost) +
4278                                strlen(luser) +
4279                                strlen(d) +
4280                                1024));
4281   *msg = 0;
4282
4283   if (!rversion || !*rversion)
4284     {
4285       sprintf (msg,
4286                _("Warning:\n\n"
4287                  "The XScreenSaver daemon doesn't seem to be running\n"
4288                  "on display \"%s\".  Launch it now?"),
4289                d);
4290     }
4291   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
4292     {
4293       /* Warn that the two processes are running as different users.
4294        */
4295       sprintf(msg,
4296             _("Warning:\n\n"
4297               "%s is running as user \"%s\" on host \"%s\".\n"
4298               "But the xscreensaver managing display \"%s\"\n"
4299               "is running as user \"%s\" on host \"%s\".\n"
4300               "\n"
4301               "Since they are different users, they won't be reading/writing\n"
4302               "the same ~/.xscreensaver file, so %s isn't\n"
4303               "going to work right.\n"
4304               "\n"
4305               "You should either re-run %s as \"%s\", or re-run\n"
4306               "xscreensaver as \"%s\".\n"
4307               "\n"
4308               "Restart the xscreensaver daemon now?\n"),
4309               progname, luser, lhost,
4310               d,
4311               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4312               progname,
4313               progname, (ruser ? ruser : "???"),
4314               luser);
4315     }
4316   else if (rhost && *rhost && !!strcmp (rhost, lhost))
4317     {
4318       /* Warn that the two processes are running on different hosts.
4319        */
4320       sprintf (msg,
4321               _("Warning:\n\n"
4322                "%s is running as user \"%s\" on host \"%s\".\n"
4323                "But the xscreensaver managing display \"%s\"\n"
4324                "is running as user \"%s\" on host \"%s\".\n"
4325                "\n"
4326                "If those two machines don't share a file system (that is,\n"
4327                "if they don't see the same ~%s/.xscreensaver file) then\n"
4328                "%s won't work right.\n"
4329                "\n"
4330                "Restart the daemon on \"%s\" as \"%s\" now?\n"),
4331                progname, luser, lhost,
4332                d,
4333                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4334                luser,
4335                progname,
4336                lhost, luser);
4337     }
4338   else if (!!strcmp (rversion, s->short_version))
4339     {
4340       /* Warn that the version numbers don't match.
4341        */
4342       sprintf (msg,
4343              _("Warning:\n\n"
4344                "This is %s version %s.\n"
4345                "But the xscreensaver managing display \"%s\"\n"
4346                "is version %s.  This could cause problems.\n"
4347                "\n"
4348                "Restart the xscreensaver daemon now?\n"),
4349                progname, s->short_version,
4350                d,
4351                rversion);
4352     }
4353
4354
4355   if (*msg)
4356     warning_dialog (s->toplevel_widget, msg, True, 1);
4357
4358   if (rversion) free (rversion);
4359   if (ruser) free (ruser);
4360   if (rhost) free (rhost);
4361   free (msg);
4362 }
4363
4364
4365 /* We use this error handler so that X errors are preceeded by the name
4366    of the program that generated them.
4367  */
4368 static int
4369 demo_ehandler (Display *dpy, XErrorEvent *error)
4370 {
4371   state *s = global_state_kludge;  /* I hate C so much... */
4372   fprintf (stderr, "\nX error in %s:\n", blurb());
4373   XmuPrintDefaultErrorMessage (dpy, error, stderr);
4374   kill_preview_subproc (s, False);
4375   exit (-1);
4376   return 0;
4377 }
4378
4379
4380 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
4381    of the program that generated them; and also that we can ignore one
4382    particular bogus error message that Gdk madly spews.
4383  */
4384 static void
4385 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
4386                const gchar *message, gpointer user_data)
4387 {
4388   /* Ignore the message "Got event for unknown window: 0x...".
4389      Apparently some events are coming in for the xscreensaver window
4390      (presumably reply events related to the ClientMessage) and Gdk
4391      feels the need to complain about them.  So, just suppress any
4392      messages that look like that one.
4393    */
4394   if (strstr (message, "unknown window"))
4395     return;
4396
4397   fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
4398            (log_domain ? log_domain : progclass),
4399            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
4400             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
4401             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
4402             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
4403             log_level == G_LOG_LEVEL_INFO     ? "info" :
4404             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
4405            message,
4406            ((!*message || message[strlen(message)-1] != '\n')
4407             ? "\n" : ""));
4408 }
4409
4410
4411 #ifdef __GNUC__
4412  __extension__     /* shut up about "string length is greater than the length
4413                       ISO C89 compilers are required to support" when including
4414                       the .ad file... */
4415 #endif
4416
4417 static char *defaults[] = {
4418 #include "XScreenSaver_ad.h"
4419  0
4420 };
4421
4422 #if 0
4423 #ifdef HAVE_CRAPPLET
4424 static struct poptOption crapplet_options[] = {
4425   {NULL, '\0', 0, NULL, 0}
4426 };
4427 #endif /* HAVE_CRAPPLET */
4428 #endif /* 0 */
4429
4430 const char *usage = "[--display dpy] [--prefs]"
4431 # ifdef HAVE_CRAPPLET
4432                     " [--crapplet]"
4433 # endif
4434             "\n\t\t   [--debug] [--sync] [--no-xshm] [--configdir dir]";
4435
4436 static void
4437 map_popup_window_cb (GtkWidget *w, gpointer user_data)
4438 {
4439   state *s = (state *) user_data;
4440   Boolean oi = s->initializing_p;
4441 #ifndef HAVE_GTK2
4442   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
4443 #endif
4444   s->initializing_p = True;
4445 #ifndef HAVE_GTK2
4446   eschew_gtk_lossage (label);
4447 #endif
4448   s->initializing_p = oi;
4449 }
4450
4451
4452 #if 0
4453 static void
4454 print_widget_tree (GtkWidget *w, int depth)
4455 {
4456   int i;
4457   for (i = 0; i < depth; i++)
4458     fprintf (stderr, "  ");
4459   fprintf (stderr, "%s\n", gtk_widget_get_name (w));
4460
4461   if (GTK_IS_LIST (w))
4462     {
4463       for (i = 0; i < depth+1; i++)
4464         fprintf (stderr, "  ");
4465       fprintf (stderr, "...list kids...\n");
4466     }
4467   else if (GTK_IS_CONTAINER (w))
4468     {
4469       GList *kids = gtk_container_children (GTK_CONTAINER (w));
4470       while (kids)
4471         {
4472           print_widget_tree (GTK_WIDGET (kids->data), depth+1);
4473           kids = kids->next;
4474         }
4475     }
4476 }
4477 #endif /* 0 */
4478
4479 static int
4480 delayed_scroll_kludge (gpointer data)
4481 {
4482   state *s = (state *) data;
4483   GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
4484   ensure_selected_item_visible (w);
4485
4486   /* Oh, this is just fucking lovely, too. */
4487   w = GTK_WIDGET (name_to_widget (s, "preview"));
4488   gtk_widget_hide (w);
4489   gtk_widget_show (w);
4490
4491   return FALSE;  /* do not re-execute timer */
4492 }
4493
4494 #ifdef HAVE_GTK2
4495
4496 GtkWidget *
4497 create_xscreensaver_demo (void)
4498 {
4499   GtkWidget *nb;
4500
4501   nb = name_to_widget (global_state_kludge, "preview_notebook");
4502   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
4503
4504   return name_to_widget (global_state_kludge, "xscreensaver_demo");
4505 }
4506
4507 GtkWidget *
4508 create_xscreensaver_settings_dialog (void)
4509 {
4510   GtkWidget *w, *box;
4511
4512   box = name_to_widget (global_state_kludge, "dialog_action_area");
4513
4514   w = name_to_widget (global_state_kludge, "adv_button");
4515   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4516
4517   w = name_to_widget (global_state_kludge, "std_button");
4518   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4519
4520   return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
4521 }
4522
4523 #endif /* HAVE_GTK2 */
4524
4525 int
4526 main (int argc, char **argv)
4527 {
4528   XtAppContext app;
4529   state S, *s;
4530   saver_preferences *p;
4531   Bool prefs = False;
4532   int i;
4533   Display *dpy;
4534   Widget toplevel_shell;
4535   char *real_progname = argv[0];
4536   char *window_title;
4537   char *geom = 0;
4538   Bool crapplet_p = False;
4539   char *str;
4540
4541 #ifdef ENABLE_NLS
4542   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
4543   textdomain (GETTEXT_PACKAGE);
4544
4545 # ifdef HAVE_GTK2
4546   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
4547 # else  /* !HAVE_GTK2 */
4548   if (!setlocale (LC_ALL, ""))
4549     fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
4550 # endif /* !HAVE_GTK2 */
4551
4552 #endif /* ENABLE_NLS */
4553
4554   str = strrchr (real_progname, '/');
4555   if (str) real_progname = str+1;
4556
4557   s = &S;
4558   memset (s, 0, sizeof(*s));
4559   s->initializing_p = True;
4560   p = &s->prefs;
4561
4562   global_state_kludge = s;  /* I hate C so much... */
4563
4564   progname = real_progname;
4565
4566   s->short_version = (char *) malloc (5);
4567   memcpy (s->short_version, screensaver_id + 17, 4);
4568   s->short_version [4] = 0;
4569
4570
4571   /* Register our error message logger for every ``log domain'' known.
4572      There's no way to do this globally, so I grepped the Gtk/Gdk sources
4573      for all of the domains that seem to be in use.
4574   */
4575   {
4576     const char * const domains[] = { 0,
4577                                      "Gtk", "Gdk", "GLib", "GModule",
4578                                      "GThread", "Gnome", "GnomeUI" };
4579     for (i = 0; i < countof(domains); i++)
4580       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
4581   }
4582
4583 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
4584 # ifndef HAVE_GTK2
4585   {
4586     const char *dir = DEFAULT_ICONDIR;
4587     if (*dir) add_pixmap_directory (dir);
4588   }
4589 # endif /* !HAVE_GTK2 */
4590 #endif /* DEFAULT_ICONDIR */
4591
4592   /* This is gross, but Gtk understands --display and not -display...
4593    */
4594   for (i = 1; i < argc; i++)
4595     if (argv[i][0] && argv[i][1] && 
4596         !strncmp(argv[i], "-display", strlen(argv[i])))
4597       argv[i] = "--display";
4598
4599
4600   /* We need to parse this arg really early... Sigh. */
4601   for (i = 1; i < argc; i++)
4602     {
4603       if (argv[i] &&
4604           (!strcmp(argv[i], "--crapplet") ||
4605            !strcmp(argv[i], "--capplet")))
4606         {
4607 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
4608           int j;
4609           crapplet_p = True;
4610           for (j = i; j < argc; j++)  /* remove it from the list */
4611             argv[j] = argv[j+1];
4612           argc--;
4613 # else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4614           fprintf (stderr, "%s: not compiled with --crapplet support\n",
4615                    real_progname);
4616           fprintf (stderr, "%s: %s\n", real_progname, usage);
4617           exit (1);
4618 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4619         }
4620       else if (argv[i] &&
4621                (!strcmp(argv[i], "--debug") ||
4622                 !strcmp(argv[i], "-debug") ||
4623                 !strcmp(argv[i], "-d")))
4624         {
4625           int j;
4626           s->debug_p = True;
4627           for (j = i; j < argc; j++)  /* remove it from the list */
4628             argv[j] = argv[j+1];
4629           argc--;
4630           i--;
4631         }
4632       else if (argv[i] &&
4633                argc > i+1 &&
4634                *argv[i+1] &&
4635                (!strcmp(argv[i], "-geometry") ||
4636                 !strcmp(argv[i], "-geom") ||
4637                 !strcmp(argv[i], "-geo") ||
4638                 !strcmp(argv[i], "-g")))
4639         {
4640           int j;
4641           geom = argv[i+1];
4642           for (j = i; j < argc; j++)  /* remove them from the list */
4643             argv[j] = argv[j+2];
4644           argc -= 2;
4645           i -= 2;
4646         }
4647       else if (argv[i] &&
4648                argc > i+1 &&
4649                *argv[i+1] &&
4650                (!strcmp(argv[i], "--configdir")))
4651         {
4652           int j;
4653           struct stat st;
4654           hack_configuration_path = argv[i+1];
4655           for (j = i; j < argc; j++)  /* remove them from the list */
4656             argv[j] = argv[j+2];
4657           argc -= 2;
4658           i -= 2;
4659
4660           if (0 != stat (hack_configuration_path, &st))
4661             {
4662               char buf[255];
4663               sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
4664               perror (buf);
4665               exit (1);
4666             }
4667           else if (!S_ISDIR (st.st_mode))
4668             {
4669               fprintf (stderr, "%s: not a directory: %s\n",
4670                        blurb(), hack_configuration_path);
4671               exit (1);
4672             }
4673         }
4674     }
4675
4676
4677   if (s->debug_p)
4678     fprintf (stderr, "%s: using config directory \"%s\"\n",
4679              progname, hack_configuration_path);
4680
4681
4682   /* Let Gtk open the X connection, then initialize Xt to use that
4683      same connection.  Doctor Frankenstein would be proud.
4684    */
4685 # ifdef HAVE_CRAPPLET
4686   if (crapplet_p)
4687     {
4688       GnomeClient *client;
4689       GnomeClientFlags flags = 0;
4690
4691       int init_results = gnome_capplet_init ("screensaver-properties",
4692                                              s->short_version,
4693                                              argc, argv, NULL, 0, NULL);
4694       /* init_results is:
4695          0 upon successful initialization;
4696          1 if --init-session-settings was passed on the cmdline;
4697          2 if --ignore was passed on the cmdline;
4698         -1 on error.
4699
4700          So the 1 signifies just to init the settings, and quit, basically.
4701          (Meaning launch the xscreensaver daemon.)
4702        */
4703
4704       if (init_results < 0)
4705         {
4706 #  if 0
4707           g_error ("An initialization error occurred while "
4708                    "starting xscreensaver-capplet.\n");
4709 #  else  /* !0 */
4710           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
4711                    real_progname, init_results);
4712           exit (1);
4713 #  endif /* !0 */
4714         }
4715
4716       client = gnome_master_client ();
4717
4718       if (client)
4719         flags = gnome_client_get_flags (client);
4720
4721       if (flags & GNOME_CLIENT_IS_CONNECTED)
4722         {
4723           int token =
4724             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
4725                                          gnome_client_get_id (client));
4726           if (token)
4727             {
4728               char *session_args[20];
4729               int i = 0;
4730               session_args[i++] = real_progname;
4731               session_args[i++] = "--capplet";
4732               session_args[i++] = "--init-session-settings";
4733               session_args[i] = 0;
4734               gnome_client_set_priority (client, 20);
4735               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
4736               gnome_client_set_restart_command (client, i, session_args);
4737             }
4738           else
4739             {
4740               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
4741             }
4742
4743           gnome_client_flush (client);
4744         }
4745
4746       if (init_results == 1)
4747         {
4748           system ("xscreensaver -nosplash &");
4749           return 0;
4750         }
4751
4752     }
4753   else
4754 # endif /* HAVE_CRAPPLET */
4755     {
4756       gtk_init (&argc, &argv);
4757     }
4758
4759
4760   /* We must read exactly the same resources as xscreensaver.
4761      That means we must have both the same progclass *and* progname,
4762      at least as far as the resource database is concerned.  So,
4763      put "xscreensaver" in argv[0] while initializing Xt.
4764    */
4765   argv[0] = "xscreensaver";
4766   progname = argv[0];
4767
4768
4769   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
4770    */
4771   XtToolkitInitialize ();
4772   app = XtCreateApplicationContext ();
4773   dpy = GDK_DISPLAY();
4774   XtAppSetFallbackResources (app, defaults);
4775   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
4776   toplevel_shell = XtAppCreateShell (progname, progclass,
4777                                      applicationShellWidgetClass,
4778                                      dpy, 0, 0);
4779
4780   dpy = XtDisplay (toplevel_shell);
4781   db = XtDatabase (dpy);
4782   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
4783   XSetErrorHandler (demo_ehandler);
4784
4785   /* Let's just ignore these.  They seem to confuse Irix Gtk... */
4786   signal (SIGPIPE, SIG_IGN);
4787
4788   /* After doing Xt-style command-line processing, complain about any
4789      unrecognized command-line arguments.
4790    */
4791   for (i = 1; i < argc; i++)
4792     {
4793       char *str = argv[i];
4794       if (str[0] == '-' && str[1] == '-')
4795         str++;
4796       if (!strcmp (str, "-prefs"))
4797         prefs = True;
4798       else if (crapplet_p)
4799         /* There are lots of random args that we don't care about when we're
4800            started as a crapplet, so just ignore unknown args in that case. */
4801         ;
4802       else
4803         {
4804           fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
4805                    argv[i]);
4806           fprintf (stderr, "%s: %s\n", real_progname, usage);
4807           exit (1);
4808         }
4809     }
4810
4811   /* Load the init file, which may end up consulting the X resource database
4812      and the site-wide app-defaults file.  Note that at this point, it's
4813      important that `progname' be "xscreensaver", rather than whatever
4814      was in argv[0].
4815    */
4816   p->db = db;
4817   s->nscreens = screen_count (dpy);
4818
4819   hack_environment (s);  /* must be before initialize_sort_map() */
4820
4821   load_init_file (dpy, p);
4822   initialize_sort_map (s);
4823
4824   /* Now that Xt has been initialized, and the resources have been read,
4825      we can set our `progname' variable to something more in line with
4826      reality.
4827    */
4828   progname = real_progname;
4829
4830
4831 #if 0
4832   /* Print out all the resources we read. */
4833   {
4834     XrmName name = { 0 };
4835     XrmClass class = { 0 };
4836     int count = 0;
4837     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
4838                           (POINTER) &count);
4839   }
4840 #endif
4841
4842
4843   /* Intern the atoms that xscreensaver_command() needs.
4844    */
4845   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
4846   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
4847   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
4848   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
4849   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
4850   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
4851   XA_SELECT = XInternAtom (dpy, "SELECT", False);
4852   XA_DEMO = XInternAtom (dpy, "DEMO", False);
4853   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
4854   XA_BLANK = XInternAtom (dpy, "BLANK", False);
4855   XA_LOCK = XInternAtom (dpy, "LOCK", False);
4856   XA_EXIT = XInternAtom (dpy, "EXIT", False);
4857   XA_RESTART = XInternAtom (dpy, "RESTART", False);
4858
4859
4860   /* Create the window and all its widgets.
4861    */
4862   s->base_widget     = create_xscreensaver_demo ();
4863   s->popup_widget    = create_xscreensaver_settings_dialog ();
4864   s->toplevel_widget = s->base_widget;
4865
4866
4867   /* Set the main window's title. */
4868   {
4869     char *base_title = _("Screensaver Preferences");
4870     char *v = (char *) strdup(strchr(screensaver_id, ' '));
4871     char *s1, *s2, *s3, *s4;
4872     s1 = (char *) strchr(v,  ' '); s1++;
4873     s2 = (char *) strchr(s1, ' ');
4874     s3 = (char *) strchr(v,  '('); s3++;
4875     s4 = (char *) strchr(s3, ')');
4876     *s2 = 0;
4877     *s4 = 0;
4878
4879     window_title = (char *) malloc (strlen (base_title) +
4880                                     strlen (progclass) +
4881                                     strlen (s1) + strlen (s3) +
4882                                     100);
4883     sprintf (window_title, "%s  (%s %s, %s)", base_title, progclass, s1, s3);
4884     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
4885     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
4886     free (v);
4887   }
4888
4889   /* Adjust the (invisible) notebooks on the popup dialog... */
4890   {
4891     GtkNotebook *notebook =
4892       GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
4893     GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
4894     int page = 0;
4895
4896 # ifdef HAVE_XML
4897     gtk_widget_hide (std);
4898 # else  /* !HAVE_XML */
4899     /* Make the advanced page be the only one available. */
4900     gtk_widget_set_sensitive (std, False);
4901     std = GTK_WIDGET (name_to_widget (s, "adv_button"));
4902     gtk_widget_hide (std);
4903     page = 1;
4904 # endif /* !HAVE_XML */
4905
4906     gtk_notebook_set_page (notebook, page);
4907     gtk_notebook_set_show_tabs (notebook, False);
4908   }
4909
4910   /* Various other widget initializations...
4911    */
4912   gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
4913                       GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4914                       (gpointer) s);
4915   gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
4916                       GTK_SIGNAL_FUNC (wm_popup_close_cb),
4917                       (gpointer) s);
4918
4919   populate_hack_list (s);
4920   populate_prefs_page (s);
4921   sensitize_demo_widgets (s, False);
4922   fix_text_entry_sizes (s);
4923   scroll_to_current_hack (s);
4924
4925   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
4926                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
4927                       (gpointer) s);
4928
4929 #ifndef HAVE_GTK2
4930   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
4931                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
4932                       (gpointer) s);
4933   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
4934                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
4935                       (gpointer) s);
4936 #endif /* !HAVE_GTK2 */
4937
4938   /* Hook up callbacks to the items on the mode menu. */
4939   {
4940     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
4941     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
4942     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
4943     int i;
4944     for (i = 0; kids; kids = kids->next, i++)
4945       {
4946         gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
4947                             GTK_SIGNAL_FUNC (mode_menu_item_cb),
4948                             (gpointer) s);
4949
4950         /* The "random-same" mode menu item does not appear unless
4951            there are multple screens.
4952          */
4953         if (s->nscreens <= 1 &&
4954             mode_menu_order[i] == RANDOM_HACKS_SAME)
4955           gtk_widget_hide (GTK_WIDGET (kids->data));
4956       }
4957
4958     if (s->nscreens <= 1)   /* recompute option-menu size */
4959       {
4960         gtk_widget_unrealize (GTK_WIDGET (menu));
4961         gtk_widget_realize (GTK_WIDGET (menu));
4962       }
4963   }
4964
4965
4966   /* Handle the -prefs command-line argument. */
4967   if (prefs)
4968     {
4969       GtkNotebook *notebook =
4970         GTK_NOTEBOOK (name_to_widget (s, "notebook"));
4971       gtk_notebook_set_page (notebook, 1);
4972     }
4973
4974 # ifdef HAVE_CRAPPLET
4975   if (crapplet_p)
4976     {
4977       GtkWidget *capplet;
4978       GtkWidget *outer_vbox;
4979
4980       gtk_widget_hide (s->toplevel_widget);
4981
4982       capplet = capplet_widget_new ();
4983
4984       /* Make there be a "Close" button instead of "OK" and "Cancel" */
4985 # ifdef HAVE_CRAPPLET_IMMEDIATE
4986       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
4987 # endif /* HAVE_CRAPPLET_IMMEDIATE */
4988       /* In crapplet-mode, take off the menubar. */
4989       gtk_widget_hide (name_to_widget (s, "menubar"));
4990
4991       /* Reparent our top-level container to be a child of the capplet
4992          window.
4993        */
4994       outer_vbox = GTK_BIN (s->toplevel_widget)->child;
4995       gtk_widget_ref (outer_vbox);
4996       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
4997                             outer_vbox);
4998       STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
4999       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
5000
5001       /* Find the window above us, and set the title and close handler. */
5002       {
5003         GtkWidget *window = capplet;
5004         while (window && !GTK_IS_WINDOW (window))
5005           window = window->parent;
5006         if (window)
5007           {
5008             gtk_window_set_title (GTK_WINDOW (window), window_title);
5009             gtk_signal_connect (GTK_OBJECT (window), "delete_event",
5010                                 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
5011                                 (gpointer) s);
5012           }
5013       }
5014
5015       s->toplevel_widget = capplet;
5016     }
5017 # endif /* HAVE_CRAPPLET */
5018
5019
5020   /* The Gnome folks hate the menubar.  I think it's important to have access
5021      to the commands on the File menu (Restart Daemon, etc.) and to the
5022      About and Documentation commands on the Help menu.
5023    */
5024 #if 0
5025 #ifdef HAVE_GTK2
5026   gtk_widget_hide (name_to_widget (s, "menubar"));
5027 #endif
5028 #endif
5029
5030   free (window_title);
5031   window_title = 0;
5032
5033 #ifdef HAVE_GTK2
5034   /* After picking the default size, allow -geometry to override it. */
5035   if (geom)
5036     gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
5037 #endif
5038
5039   gtk_widget_show (s->toplevel_widget);
5040   init_icon (GTK_WIDGET (s->toplevel_widget)->window);  /* after `show' */
5041   fix_preview_visual (s);
5042
5043   /* Realize page zero, so that we can diddle the scrollbar when the
5044      user tabs back to it -- otherwise, the current hack isn't scrolled
5045      to the first time they tab back there, when started with "-prefs".
5046      (Though it is if they then tab away, and back again.)
5047
5048      #### Bah!  This doesn't work.  Gtk eats my ass!  Someone who
5049      #### understands this crap, explain to me how to make this work.
5050   */
5051   gtk_widget_realize (name_to_widget (s, "demos_table"));
5052
5053
5054   gtk_timeout_add (60 * 1000, check_blanked_timer, s);
5055
5056
5057   /* Issue any warnings about the running xscreensaver daemon. */
5058   if (! s->debug_p)
5059     the_network_is_not_the_computer (s);
5060
5061
5062   /* Run the Gtk event loop, and not the Xt event loop.  This means that
5063      if there were Xt timers or fds registered, they would never get serviced,
5064      and if there were any Xt widgets, they would never have events delivered.
5065      Fortunately, we're using Gtk for all of the UI, and only initialized
5066      Xt so that we could process the command line and use the X resource
5067      manager.
5068    */
5069   s->initializing_p = False;
5070
5071   /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
5072      after we start up.  Otherwise, it always appears scrolled to the top
5073      when in crapplet-mode. */
5074   gtk_timeout_add (500, delayed_scroll_kludge, s);
5075
5076
5077 #if 1
5078   /* Load every configurator in turn, to scan them for errors all at once. */
5079   if (s->debug_p)
5080     {
5081       int i;
5082       for (i = 0; i < p->screenhacks_count; i++)
5083         {
5084           screenhack *hack = p->screenhacks[i];
5085           conf_data *d = load_configurator (hack->command, s->debug_p);
5086           if (d) free_conf_data (d);
5087         }
5088     }
5089 #endif
5090
5091
5092 # ifdef HAVE_CRAPPLET
5093   if (crapplet_p)
5094     capplet_gtk_main ();
5095   else
5096 # endif /* HAVE_CRAPPLET */
5097     gtk_main ();
5098
5099   kill_preview_subproc (s, False);
5100   exit (0);
5101 }
5102
5103 #endif /* HAVE_GTK -- whole file */