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