http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2008 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   MINUTES  (&p2->dpms_standby,    "dpms_standby_spinbutton");
1553   MINUTES  (&p2->dpms_suspend,    "dpms_suspend_spinbutton");
1554   MINUTES  (&p2->dpms_off,        "dpms_off_spinbutton");
1555
1556   CHECKBOX (p2->grab_desktop_p,   "grab_desk_button");
1557   CHECKBOX (p2->grab_video_p,     "grab_video_button");
1558   CHECKBOX (p2->random_image_p,   "grab_image_button");
1559   PATHNAME (p2->image_directory,  "image_text");
1560
1561 #if 0
1562   CHECKBOX (p2->verbose_p,        "verbose_button");
1563   CHECKBOX (p2->capture_stderr_p, "capture_button");
1564   CHECKBOX (p2->splash_p,         "splash_button");
1565 #endif
1566
1567   {
1568     Bool v = False;
1569     CHECKBOX (v, "text_host_radio");     if (v) p2->tmode = TEXT_DATE;
1570     CHECKBOX (v, "text_radio");          if (v) p2->tmode = TEXT_LITERAL;
1571     CHECKBOX (v, "text_file_radio");     if (v) p2->tmode = TEXT_FILE;
1572     CHECKBOX (v, "text_program_radio");  if (v) p2->tmode = TEXT_PROGRAM;
1573     CHECKBOX (v, "text_url_radio");      if (v) p2->tmode = TEXT_URL;
1574     TEXT     (p2->text_literal, "text_entry");
1575     PATHNAME (p2->text_file,    "text_file_entry");
1576     PATHNAME (p2->text_program, "text_program_entry");
1577     PATHNAME (p2->text_program, "text_program_entry");
1578     TEXT     (p2->text_url,     "text_url_entry");
1579   }
1580
1581   CHECKBOX (p2->install_cmap_p,   "install_button");
1582   CHECKBOX (p2->fade_p,           "fade_button");
1583   CHECKBOX (p2->unfade_p,         "unfade_button");
1584   SECONDS  (&p2->fade_seconds,    "fade_spinbutton");
1585
1586 # undef SECONDS
1587 # undef MINUTES
1588 # undef CHECKBOX
1589 # undef PATHNAME
1590 # undef TEXT
1591
1592   /* Warn if the image directory doesn't exist, when:
1593      - not being warned before
1594      - image directory is changed and the directory doesn't exist
1595    */
1596   if (p2->image_directory &&
1597       *p2->image_directory &&
1598       !directory_p (p2->image_directory) &&
1599         ( !already_warned_about_missing_image_directory ||
1600           ( p->image_directory &&
1601             *p->image_directory &&
1602             strcmp(p->image_directory, p2->image_directory)
1603           )
1604         )
1605       )
1606     {
1607       char b[255];
1608       sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n",
1609                p2->image_directory);
1610       if (warning_dialog (s->toplevel_widget, b, D_NONE, 100))
1611         already_warned_about_missing_image_directory = True;
1612     }
1613
1614
1615   /* Map the mode menu to `saver_mode' enum values. */
1616   {
1617     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
1618     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1619     GtkWidget *selected = gtk_menu_get_active (menu);
1620     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1621     int menu_elt = g_list_index (kids, (gpointer) selected);
1622     if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
1623     p2->mode = mode_menu_order[menu_elt];
1624   }
1625
1626   if (p2->mode == ONE_HACK)
1627     {
1628       int list_elt = selected_list_element (s);
1629       p2->selected_hack = (list_elt >= 0
1630                            ? s->list_elt_to_hack_number[list_elt]
1631                            : -1);
1632     }
1633
1634 # define COPY(field, name) \
1635   if (p->field != p2->field) { \
1636     changed = True; \
1637     if (s->debug_p) \
1638       fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
1639   } \
1640   p->field = p2->field
1641
1642   COPY(mode,             "mode");
1643   COPY(selected_hack,    "selected_hack");
1644
1645   COPY(timeout,        "timeout");
1646   COPY(cycle,          "cycle");
1647   COPY(lock_p,         "lock_p");
1648   COPY(lock_timeout,   "lock_timeout");
1649
1650   COPY(dpms_enabled_p, "dpms_enabled_p");
1651   COPY(dpms_standby,   "dpms_standby");
1652   COPY(dpms_suspend,   "dpms_suspend");
1653   COPY(dpms_off,       "dpms_off");
1654
1655 #if 0
1656   COPY(verbose_p,        "verbose_p");
1657   COPY(capture_stderr_p, "capture_stderr_p");
1658   COPY(splash_p,         "splash_p");
1659 #endif
1660
1661   COPY(tmode,            "tmode");
1662
1663   COPY(install_cmap_p,   "install_cmap_p");
1664   COPY(fade_p,           "fade_p");
1665   COPY(unfade_p,         "unfade_p");
1666   COPY(fade_seconds,     "fade_seconds");
1667
1668   COPY(grab_desktop_p, "grab_desktop_p");
1669   COPY(grab_video_p,   "grab_video_p");
1670   COPY(random_image_p, "random_image_p");
1671
1672 # undef COPY
1673
1674 # define COPYSTR(FIELD,NAME) \
1675   if (!p->FIELD || \
1676       !p2->FIELD || \
1677       strcmp(p->FIELD, p2->FIELD)) \
1678     { \
1679       changed = True; \
1680       if (s->debug_p) \
1681         fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
1682     } \
1683   if (p->FIELD && p->FIELD != p2->FIELD) \
1684     free (p->FIELD); \
1685   p->FIELD = p2->FIELD; \
1686   p2->FIELD = 0
1687
1688   COPYSTR(image_directory, "image_directory");
1689   COPYSTR(text_literal,    "text_literal");
1690   COPYSTR(text_file,       "text_file");
1691   COPYSTR(text_program,    "text_program");
1692   COPYSTR(text_url,        "text_url");
1693 # undef COPYSTR
1694
1695   populate_prefs_page (s);
1696
1697   if (changed)
1698     {
1699       Display *dpy = GDK_DISPLAY();
1700       Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
1701       sync_server_dpms_settings (dpy, enabled_p,
1702                                  p->dpms_standby / 1000,
1703                                  p->dpms_suspend / 1000,
1704                                  p->dpms_off / 1000,
1705                                  False);
1706
1707       changed = demo_write_init_file (s, p);
1708     }
1709
1710   s->saving_p = False;
1711   return changed;
1712 }
1713
1714
1715 /* Flush out any changes made in the popup dialog box (where changes
1716    take place only when the OK button is clicked.)
1717  */
1718 static Bool
1719 flush_popup_changes_and_save (state *s)
1720 {
1721   Bool changed = False;
1722   saver_preferences *p = &s->prefs;
1723   int list_elt = selected_list_element (s);
1724
1725   GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
1726   GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
1727
1728   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
1729   const char *command = gtk_entry_get_text (cmd);
1730
1731   char c;
1732   unsigned long id;
1733
1734   if (s->saving_p) return False;
1735   s->saving_p = True;
1736
1737   if (list_elt < 0)
1738     goto DONE;
1739
1740   if (maybe_reload_init_file (s) != 0)
1741     {
1742       changed = True;
1743       goto DONE;
1744     }
1745
1746   /* Sanity-check and canonicalize whatever the user typed into the combo box.
1747    */
1748   if      (!strcasecmp (visual, ""))                   visual = "";
1749   else if (!strcasecmp (visual, "any"))                visual = "";
1750   else if (!strcasecmp (visual, "default"))            visual = "Default";
1751   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
1752   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
1753   else if (!strcasecmp (visual, "best"))               visual = "Best";
1754   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
1755   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
1756   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
1757   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
1758   else if (!strcasecmp (visual, "color"))              visual = "Color";
1759   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
1760   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
1761   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
1762   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
1763   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
1764   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
1765   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
1766   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
1767   else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
1768   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
1769   else
1770     {
1771       gdk_beep ();                                /* unparsable */
1772       visual = "";
1773       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
1774     }
1775
1776   changed = flush_changes (s, list_elt, -1, command, visual);
1777   if (changed)
1778     {
1779       changed = demo_write_init_file (s, p);
1780
1781       /* Do this to re-launch the hack if (and only if) the command line
1782          has changed. */
1783       populate_demo_window (s, selected_list_element (s));
1784     }
1785
1786  DONE:
1787   s->saving_p = False;
1788   return changed;
1789 }
1790
1791
1792 G_MODULE_EXPORT void
1793 pref_changed_cb (GtkWidget *widget, gpointer user_data)
1794 {
1795   state *s = global_state_kludge;  /* I hate C so much... */
1796   if (! s->initializing_p)
1797     {
1798       s->initializing_p = True;
1799       flush_dialog_changes_and_save (s);
1800       s->initializing_p = False;
1801     }
1802 }
1803
1804 G_MODULE_EXPORT gboolean
1805 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1806 {
1807   pref_changed_cb (widget, user_data);
1808   return FALSE;
1809 }
1810
1811 /* Callback on menu items in the "mode" options menu.
1812  */
1813 G_MODULE_EXPORT void
1814 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
1815 {
1816   state *s = (state *) user_data;
1817   saver_preferences *p = &s->prefs;
1818   GtkWidget *list = name_to_widget (s, "list");
1819   int list_elt;
1820
1821   GList *menu_items =
1822     gtk_container_children (GTK_CONTAINER (GET_PARENT (widget)));
1823   int menu_index = 0;
1824   saver_mode new_mode;
1825
1826   while (menu_items)
1827     {
1828       if (menu_items->data == widget)
1829         break;
1830       menu_index++;
1831       menu_items = menu_items->next;
1832     }
1833   if (!menu_items) abort();
1834
1835   new_mode = mode_menu_order[menu_index];
1836
1837   /* Keep the same list element displayed as before; except if we're
1838      switching *to* "one screensaver" mode from any other mode, set
1839      "the one" to be that which is currently selected.
1840    */
1841   list_elt = selected_list_element (s);
1842   if (new_mode == ONE_HACK)
1843     p->selected_hack = s->list_elt_to_hack_number[list_elt];
1844
1845   {
1846     saver_mode old_mode = p->mode;
1847     p->mode = new_mode;
1848     populate_demo_window (s, list_elt);
1849     force_list_select_item (s, list, list_elt, True);
1850     p->mode = old_mode;  /* put it back, so the init file gets written */
1851   }
1852
1853   pref_changed_cb (widget, user_data);
1854 }
1855
1856
1857 G_MODULE_EXPORT void
1858 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1859                 gint page_num, gpointer user_data)
1860 {
1861   state *s = global_state_kludge;  /* I hate C so much... */
1862   pref_changed_cb (GTK_WIDGET (notebook), user_data);
1863
1864   /* If we're switching to page 0, schedule the current hack to be run.
1865      Otherwise, schedule it to stop. */
1866   if (page_num == 0)
1867     populate_demo_window (s, selected_list_element (s));
1868   else
1869     schedule_preview (s, 0);
1870 }
1871
1872 #ifdef HAVE_GTK2
1873 static void
1874 list_activated_cb (GtkTreeView       *list,
1875                    GtkTreePath       *path,
1876                    GtkTreeViewColumn *column,
1877                    gpointer           data)
1878 {
1879   state *s = data;
1880   char *str;
1881   int list_elt;
1882
1883   g_return_if_fail (!gdk_pointer_is_grabbed ());
1884
1885   str = gtk_tree_path_to_string (path);
1886   list_elt = strtol (str, NULL, 10);
1887   g_free (str);
1888
1889   if (list_elt >= 0)
1890     run_hack (s, list_elt, True);
1891 }
1892
1893 static void
1894 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
1895 {
1896   state *s = (state *)data;
1897   GtkTreeModel *model;
1898   GtkTreeIter iter;
1899   GtkTreePath *path;
1900   char *str;
1901   int list_elt;
1902  
1903   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1904     return;
1905
1906   path = gtk_tree_model_get_path (model, &iter);
1907   str = gtk_tree_path_to_string (path);
1908   list_elt = strtol (str, NULL, 10);
1909
1910   gtk_tree_path_free (path);
1911   g_free (str);
1912
1913   populate_demo_window (s, list_elt);
1914   flush_dialog_changes_and_save (s);
1915
1916   /* Re-populate the Settings window any time a new item is selected
1917      in the list, in case both windows are currently visible.
1918    */
1919   populate_popup_window (s);
1920 }
1921
1922 #else /* !HAVE_GTK2 */
1923
1924 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
1925                                               list_select_cb that comes in
1926                                               *after* we've double-clicked.
1927                                             */
1928
1929 static gint
1930 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1931                      gpointer data)
1932 {
1933   state *s = (state *) data;
1934   if (event->type == GDK_2BUTTON_PRESS)
1935     {
1936       GtkList *list = GTK_LIST (name_to_widget (s, "list"));
1937       int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
1938
1939       last_doubleclick_time = time ((time_t *) 0);
1940
1941       if (list_elt >= 0)
1942         run_hack (s, list_elt, True);
1943     }
1944
1945   return FALSE;
1946 }
1947
1948
1949 static void
1950 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
1951 {
1952   state *s = (state *) data;
1953   time_t now = time ((time_t *) 0);
1954
1955   if (now >= last_doubleclick_time + 2)
1956     {
1957       int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
1958       populate_demo_window (s, list_elt);
1959       flush_dialog_changes_and_save (s);
1960     }
1961 }
1962
1963 static void
1964 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
1965 {
1966   state *s = (state *) data;
1967   populate_demo_window (s, -1);
1968   flush_dialog_changes_and_save (s);
1969 }
1970
1971 #endif /* !HAVE_GTK2 */
1972
1973
1974 /* Called when the checkboxes that are in the left column of the
1975    scrolling list are clicked.  This both populates the right pane
1976    (just as clicking on the label (really, listitem) does) and
1977    also syncs this checkbox with  the right pane Enabled checkbox.
1978  */
1979 static void
1980 list_checkbox_cb (
1981 #ifdef HAVE_GTK2
1982                   GtkCellRendererToggle *toggle,
1983                   gchar                 *path_string,
1984 #else  /* !HAVE_GTK2 */
1985                   GtkWidget *cb,
1986 #endif /* !HAVE_GTK2 */
1987                   gpointer               data)
1988 {
1989   state *s = (state *) data;
1990
1991 #ifdef HAVE_GTK2
1992   GtkScrolledWindow *scroller =
1993     GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
1994   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
1995   GtkTreeModel *model = gtk_tree_view_get_model (list);
1996   GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1997   GtkTreeIter iter;
1998   gboolean active;
1999 #else /* !HAVE_GTK2 */
2000   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
2001   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
2002
2003   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
2004   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
2005   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
2006 #endif /* !HAVE_GTK2 */
2007   GtkAdjustment *adj;
2008   double scroll_top;
2009
2010   int list_elt;
2011
2012 #ifdef HAVE_GTK2
2013   if (!gtk_tree_model_get_iter (model, &iter, path))
2014     {
2015       g_warning ("bad path: %s", path_string);
2016       return;
2017     }
2018   gtk_tree_path_free (path);
2019
2020   gtk_tree_model_get (model, &iter,
2021                       COL_ENABLED, &active,
2022                       -1);
2023
2024   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
2025                       COL_ENABLED, !active,
2026                       -1);
2027
2028   list_elt = strtol (path_string, NULL, 10);  
2029 #else  /* !HAVE_GTK2 */
2030   list_elt = gtk_list_child_position (list, line);
2031 #endif /* !HAVE_GTK2 */
2032
2033   /* remember previous scroll position of the top of the list */
2034   adj = gtk_scrolled_window_get_vadjustment (scroller);
2035   scroll_top = GET_ADJ_VALUE (adj);
2036
2037   flush_dialog_changes_and_save (s);
2038   force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
2039   populate_demo_window (s, list_elt);
2040   
2041   /* restore the previous scroll position of the top of the list.
2042      this is weak, but I don't really know why it's moving... */
2043   gtk_adjustment_set_value (adj, scroll_top);
2044 }
2045
2046
2047 typedef struct {
2048   state *state;
2049   GtkFileSelection *widget;
2050 } file_selection_data;
2051
2052
2053
2054 static void
2055 store_image_directory (GtkWidget *button, gpointer user_data)
2056 {
2057   file_selection_data *fsd = (file_selection_data *) user_data;
2058   state *s = fsd->state;
2059   GtkFileSelection *selector = fsd->widget;
2060   GtkWidget *top = s->toplevel_widget;
2061   saver_preferences *p = &s->prefs;
2062   const char *path = gtk_file_selection_get_filename (selector);
2063
2064   if (p->image_directory && !strcmp(p->image_directory, path))
2065     return;  /* no change */
2066
2067   if (!directory_p (path))
2068     {
2069       char b[255];
2070       sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
2071       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2072       return;
2073     }
2074
2075   if (p->image_directory) free (p->image_directory);
2076   p->image_directory = normalize_directory (path);
2077
2078   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2079                       (p->image_directory ? p->image_directory : ""));
2080   demo_write_init_file (s, p);
2081 }
2082
2083
2084 static void
2085 store_text_file (GtkWidget *button, gpointer user_data)
2086 {
2087   file_selection_data *fsd = (file_selection_data *) user_data;
2088   state *s = fsd->state;
2089   GtkFileSelection *selector = fsd->widget;
2090   GtkWidget *top = s->toplevel_widget;
2091   saver_preferences *p = &s->prefs;
2092   const char *path = gtk_file_selection_get_filename (selector);
2093
2094   if (p->text_file && !strcmp(p->text_file, path))
2095     return;  /* no change */
2096
2097   if (!file_p (path))
2098     {
2099       char b[255];
2100       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2101       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2102       return;
2103     }
2104
2105   if (p->text_file) free (p->text_file);
2106   p->text_file = normalize_directory (path);
2107
2108   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2109                       (p->text_file ? p->text_file : ""));
2110   demo_write_init_file (s, p);
2111 }
2112
2113
2114 static void
2115 store_text_program (GtkWidget *button, gpointer user_data)
2116 {
2117   file_selection_data *fsd = (file_selection_data *) user_data;
2118   state *s = fsd->state;
2119   GtkFileSelection *selector = fsd->widget;
2120   /*GtkWidget *top = s->toplevel_widget;*/
2121   saver_preferences *p = &s->prefs;
2122   const char *path = gtk_file_selection_get_filename (selector);
2123
2124   if (p->text_program && !strcmp(p->text_program, path))
2125     return;  /* no change */
2126
2127 # if 0
2128   if (!file_p (path))
2129     {
2130       char b[255];
2131       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2132       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2133       return;
2134     }
2135 # endif
2136
2137   if (p->text_program) free (p->text_program);
2138   p->text_program = normalize_directory (path);
2139
2140   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2141                       (p->text_program ? p->text_program : ""));
2142   demo_write_init_file (s, p);
2143 }
2144
2145
2146
2147 static void
2148 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
2149 {
2150   file_selection_data *fsd = (file_selection_data *) user_data;
2151   gtk_widget_hide (GTK_WIDGET (fsd->widget));
2152 }
2153
2154 static void
2155 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
2156 {
2157   browse_image_dir_cancel (button, user_data);
2158   store_image_directory (button, user_data);
2159 }
2160
2161 static void
2162 browse_text_file_ok (GtkWidget *button, gpointer user_data)
2163 {
2164   browse_image_dir_cancel (button, user_data);
2165   store_text_file (button, user_data);
2166 }
2167
2168 static void
2169 browse_text_program_ok (GtkWidget *button, gpointer user_data)
2170 {
2171   browse_image_dir_cancel (button, user_data);
2172   store_text_program (button, user_data);
2173 }
2174
2175 static void
2176 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
2177 {
2178   browse_image_dir_cancel (widget, user_data);
2179 }
2180
2181
2182 G_MODULE_EXPORT void
2183 browse_image_dir_cb (GtkButton *button, gpointer user_data)
2184 {
2185   state *s = global_state_kludge;  /* I hate C so much... */
2186   saver_preferences *p = &s->prefs;
2187   static file_selection_data *fsd = 0;
2188
2189   GtkFileSelection *selector = GTK_FILE_SELECTION(
2190     gtk_file_selection_new ("Please select the image directory."));
2191
2192   if (!fsd)
2193     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2194
2195   fsd->widget = selector;
2196   fsd->state = s;
2197
2198   if (p->image_directory && *p->image_directory)
2199     gtk_file_selection_set_filename (selector, p->image_directory);
2200
2201   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2202                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
2203                       (gpointer *) fsd);
2204   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2205                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2206                       (gpointer *) fsd);
2207   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2208                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2209                       (gpointer *) fsd);
2210
2211   gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
2212
2213   gtk_window_set_modal (GTK_WINDOW (selector), True);
2214   gtk_widget_show (GTK_WIDGET (selector));
2215 }
2216
2217
2218 G_MODULE_EXPORT void
2219 browse_text_file_cb (GtkButton *button, gpointer user_data)
2220 {
2221   state *s = global_state_kludge;  /* I hate C so much... */
2222   saver_preferences *p = &s->prefs;
2223   static file_selection_data *fsd = 0;
2224
2225   GtkFileSelection *selector = GTK_FILE_SELECTION(
2226     gtk_file_selection_new ("Please select a text file."));
2227
2228   if (!fsd)
2229     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2230
2231   fsd->widget = selector;
2232   fsd->state = s;
2233
2234   if (p->text_file && *p->text_file)
2235     gtk_file_selection_set_filename (selector, p->text_file);
2236
2237   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2238                       "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
2239                       (gpointer *) fsd);
2240   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2241                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2242                       (gpointer *) fsd);
2243   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2244                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2245                       (gpointer *) fsd);
2246
2247   gtk_window_set_modal (GTK_WINDOW (selector), True);
2248   gtk_widget_show (GTK_WIDGET (selector));
2249 }
2250
2251
2252 G_MODULE_EXPORT void
2253 browse_text_program_cb (GtkButton *button, gpointer user_data)
2254 {
2255   state *s = global_state_kludge;  /* I hate C so much... */
2256   saver_preferences *p = &s->prefs;
2257   static file_selection_data *fsd = 0;
2258
2259   GtkFileSelection *selector = GTK_FILE_SELECTION(
2260     gtk_file_selection_new ("Please select a text-generating program."));
2261
2262   if (!fsd)
2263     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2264
2265   fsd->widget = selector;
2266   fsd->state = s;
2267
2268   if (p->text_program && *p->text_program)
2269     gtk_file_selection_set_filename (selector, p->text_program);
2270
2271   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2272                       "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
2273                       (gpointer *) fsd);
2274   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2275                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2276                       (gpointer *) fsd);
2277   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2278                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2279                       (gpointer *) fsd);
2280
2281   gtk_window_set_modal (GTK_WINDOW (selector), True);
2282   gtk_widget_show (GTK_WIDGET (selector));
2283 }
2284
2285
2286
2287
2288
2289 G_MODULE_EXPORT void
2290 settings_cb (GtkButton *button, gpointer user_data)
2291 {
2292   state *s = global_state_kludge;  /* I hate C so much... */
2293   int list_elt = selected_list_element (s);
2294
2295   populate_demo_window (s, list_elt);   /* reset the widget */
2296   populate_popup_window (s);            /* create UI on popup window */
2297   gtk_widget_show (s->popup_widget);
2298 }
2299
2300 static void
2301 settings_sync_cmd_text (state *s)
2302 {
2303 # ifdef HAVE_XML
2304   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2305   char *cmd_line = get_configurator_command_line (s->cdata, False);
2306   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2307   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2308   free (cmd_line);
2309 # endif /* HAVE_XML */
2310 }
2311
2312 G_MODULE_EXPORT void
2313 settings_adv_cb (GtkButton *button, gpointer user_data)
2314 {
2315   state *s = global_state_kludge;  /* I hate C so much... */
2316   GtkNotebook *notebook =
2317     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2318
2319   settings_sync_cmd_text (s);
2320   gtk_notebook_set_page (notebook, 1);
2321 }
2322
2323 G_MODULE_EXPORT void
2324 settings_std_cb (GtkButton *button, gpointer user_data)
2325 {
2326   state *s = global_state_kludge;  /* I hate C so much... */
2327   GtkNotebook *notebook =
2328     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2329
2330   /* Re-create UI to reflect the in-progress command-line settings. */
2331   populate_popup_window (s);
2332
2333   gtk_notebook_set_page (notebook, 0);
2334 }
2335
2336 G_MODULE_EXPORT void
2337 settings_reset_cb (GtkButton *button, gpointer user_data)
2338 {
2339 # ifdef HAVE_XML
2340   state *s = global_state_kludge;  /* I hate C so much... */
2341   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2342   char *cmd_line = get_configurator_command_line (s->cdata, True);
2343   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2344   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2345   free (cmd_line);
2346   populate_popup_window (s);
2347 # endif /* HAVE_XML */
2348 }
2349
2350 G_MODULE_EXPORT void
2351 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
2352                          gint page_num, gpointer user_data)
2353 {
2354   state *s = global_state_kludge;  /* I hate C so much... */
2355   GtkWidget *adv = name_to_widget (s, "adv_button");
2356   GtkWidget *std = name_to_widget (s, "std_button");
2357
2358   if (page_num == 0)
2359     {
2360       gtk_widget_show (adv);
2361       gtk_widget_hide (std);
2362     }
2363   else if (page_num == 1)
2364     {
2365       gtk_widget_hide (adv);
2366       gtk_widget_show (std);
2367     }
2368   else
2369     abort();
2370 }
2371
2372
2373
2374 G_MODULE_EXPORT void
2375 settings_cancel_cb (GtkButton *button, gpointer user_data)
2376 {
2377   state *s = global_state_kludge;  /* I hate C so much... */
2378   gtk_widget_hide (s->popup_widget);
2379 }
2380
2381 G_MODULE_EXPORT void
2382 settings_ok_cb (GtkButton *button, gpointer user_data)
2383 {
2384   state *s = global_state_kludge;  /* I hate C so much... */
2385   GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2386   int page = gtk_notebook_get_current_page (notebook);
2387
2388   if (page == 0)
2389     /* Regenerate the command-line from the widget contents before saving.
2390        But don't do this if we're looking at the command-line page already,
2391        or we will blow away what they typed... */
2392     settings_sync_cmd_text (s);
2393
2394   flush_popup_changes_and_save (s);
2395   gtk_widget_hide (s->popup_widget);
2396 }
2397
2398 static gboolean
2399 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
2400 {
2401   state *s = (state *) data;
2402   settings_cancel_cb (0, (gpointer) s);
2403   return TRUE;
2404 }
2405
2406
2407 \f
2408 /* Populating the various widgets
2409  */
2410
2411
2412 /* Returns the number of the last hack run by the server.
2413  */
2414 static int
2415 server_current_hack (void)
2416 {
2417   Atom type;
2418   int format;
2419   unsigned long nitems, bytesafter;
2420   unsigned char *dataP = 0;
2421   Display *dpy = GDK_DISPLAY();
2422   int hack_number = -1;
2423
2424   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
2425                           XA_SCREENSAVER_STATUS,
2426                           0, 3, False, XA_INTEGER,
2427                           &type, &format, &nitems, &bytesafter,
2428                           &dataP)
2429       == Success
2430       && type == XA_INTEGER
2431       && nitems >= 3
2432       && dataP)
2433     {
2434       PROP32 *data = (PROP32 *) dataP;
2435       hack_number = (int) data[2] - 1;
2436     }
2437
2438   if (dataP) XFree (dataP);
2439
2440   return hack_number;
2441 }
2442
2443
2444 /* Finds the number of the last hack that was run, and makes that item be
2445    selected by default.
2446  */
2447 static void
2448 scroll_to_current_hack (state *s)
2449 {
2450   saver_preferences *p = &s->prefs;
2451   int hack_number = -1;
2452
2453   if (p->mode == ONE_HACK)                 /* in "one" mode, use the one */
2454     hack_number = p->selected_hack;
2455   if (hack_number < 0)                     /* otherwise, use the last-run */
2456     hack_number = server_current_hack ();
2457   if (hack_number < 0)                     /* failing that, last "one mode" */
2458     hack_number = p->selected_hack;
2459   if (hack_number < 0)                     /* failing that, newest hack. */
2460     {
2461       /* We should only get here if the user does not have a .xscreensaver
2462          file, and the screen has not been blanked with a hack since X
2463          started up: in other words, this is probably a fresh install.
2464
2465          Instead of just defaulting to hack #0 (in either "programs" or
2466          "alphabetical" order) let's try to default to the last runnable
2467          hack in the "programs" list: this is probably the hack that was
2468          most recently added to the xscreensaver distribution (and so
2469          it's probably the currently-coolest one!)
2470        */
2471       hack_number = p->screenhacks_count-1;
2472       while (hack_number > 0 &&
2473              ! (s->hacks_available_p[hack_number] &&
2474                 p->screenhacks[hack_number]->enabled_p))
2475         hack_number--;
2476     }
2477
2478   if (hack_number >= 0 && hack_number < p->screenhacks_count)
2479     {
2480       int list_elt = s->hack_number_to_list_elt[hack_number];
2481       GtkWidget *list = name_to_widget (s, "list");
2482       force_list_select_item (s, list, list_elt, True);
2483       populate_demo_window (s, list_elt);
2484     }
2485 }
2486
2487
2488 static void
2489 populate_hack_list (state *s)
2490 {
2491   Display *dpy = GDK_DISPLAY();
2492 #ifdef HAVE_GTK2
2493   saver_preferences *p = &s->prefs;
2494   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2495   GtkListStore *model;
2496   GtkTreeSelection *selection;
2497   GtkCellRenderer *ren;
2498   GtkTreeIter iter;
2499   int i;
2500
2501   g_object_get (G_OBJECT (list),
2502                 "model", &model,
2503                 NULL);
2504   if (!model)
2505     {
2506       model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
2507       g_object_set (G_OBJECT (list), "model", model, NULL);
2508       g_object_unref (model);
2509
2510       ren = gtk_cell_renderer_toggle_new ();
2511       gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
2512                                                    _("Use"), ren,
2513                                                    "active", COL_ENABLED,
2514                                                    NULL);
2515
2516       g_signal_connect (ren, "toggled",
2517                         G_CALLBACK (list_checkbox_cb),
2518                         s);
2519
2520       ren = gtk_cell_renderer_text_new ();
2521       gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
2522                                                    _("Screen Saver"), ren,
2523                                                    "markup", COL_NAME,
2524                                                    NULL);
2525
2526       g_signal_connect_after (list, "row_activated",
2527                               G_CALLBACK (list_activated_cb),
2528                               s);
2529
2530       selection = gtk_tree_view_get_selection (list);
2531       g_signal_connect (selection, "changed",
2532                         G_CALLBACK (list_select_changed_cb),
2533                         s);
2534
2535     }
2536
2537   for (i = 0; i < s->list_count; i++)
2538     {
2539       int hack_number = s->list_elt_to_hack_number[i];
2540       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2541       char *pretty_name;
2542       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2543
2544       if (!hack) continue;
2545
2546       /* If we're to suppress uninstalled hacks, check $PATH now. */
2547       if (p->ignore_uninstalled_p && !available_p)
2548         continue;
2549
2550       pretty_name = (hack->name
2551                      ? strdup (hack->name)
2552                      : make_hack_name (dpy, hack->command));
2553
2554       if (!available_p)
2555         {
2556           /* Make the text foreground be the color of insensitive widgets
2557              (but don't actually make it be insensitive, since we still
2558              want to be able to click on it.)
2559            */
2560           GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list));
2561           GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
2562        /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
2563           char *buf = (char *) malloc (strlen (pretty_name) + 100);
2564
2565           sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
2566                       /*     " background=\"#%02X%02X%02X\""  */
2567                         ">%s</span>",
2568                    fg->red >> 8, fg->green >> 8, fg->blue >> 8,
2569                 /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
2570                    pretty_name);
2571           free (pretty_name);
2572           pretty_name = buf;
2573         }
2574
2575       gtk_list_store_append (model, &iter);
2576       gtk_list_store_set (model, &iter,
2577                           COL_ENABLED, hack->enabled_p,
2578                           COL_NAME, pretty_name,
2579                           -1);
2580       free (pretty_name);
2581     }
2582
2583 #else /* !HAVE_GTK2 */
2584
2585   saver_preferences *p = &s->prefs;
2586   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2587   int i;
2588   for (i = 0; i < s->list_count; i++)
2589     {
2590       int hack_number = s->list_elt_to_hack_number[i];
2591       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2592
2593       /* A GtkList must contain only GtkListItems, but those can contain
2594          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
2595          and a Label.  We handle single and double click events on the
2596          line itself, for clicking on the text, but the interior checkbox
2597          also handles its own events.
2598        */
2599       GtkWidget *line;
2600       GtkWidget *line_hbox;
2601       GtkWidget *line_check;
2602       GtkWidget *line_label;
2603       char *pretty_name;
2604       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2605
2606       if (!hack) continue;
2607
2608       /* If we're to suppress uninstalled hacks, check $PATH now. */
2609       if (p->ignore_uninstalled_p && !available_p)
2610         continue;
2611
2612       pretty_name = (hack->name
2613                      ? strdup (hack->name)
2614                      : make_hack_name (hack->command));
2615
2616       line = gtk_list_item_new ();
2617       line_hbox = gtk_hbox_new (FALSE, 0);
2618       line_check = gtk_check_button_new ();
2619       line_label = gtk_label_new (pretty_name);
2620
2621       gtk_container_add (GTK_CONTAINER (line), line_hbox);
2622       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
2623       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
2624
2625       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
2626                                     hack->enabled_p);
2627       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
2628
2629       gtk_widget_show (line_check);
2630       gtk_widget_show (line_label);
2631       gtk_widget_show (line_hbox);
2632       gtk_widget_show (line);
2633
2634       free (pretty_name);
2635
2636       gtk_container_add (GTK_CONTAINER (list), line);
2637       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
2638                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
2639                           (gpointer) s);
2640
2641       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
2642                           GTK_SIGNAL_FUNC (list_checkbox_cb),
2643                           (gpointer) s);
2644
2645       gtk_widget_show (line);
2646
2647       if (!available_p)
2648         {
2649           /* Make the widget be colored like insensitive widgets
2650              (but don't actually make it be insensitive, since we
2651              still want to be able to click on it.)
2652            */
2653           GtkRcStyle *rc_style;
2654           GdkColor fg, bg;
2655
2656           gtk_widget_realize (GTK_WIDGET (line_label));
2657
2658           fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
2659           bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
2660
2661           rc_style = gtk_rc_style_new ();
2662           rc_style->fg[GTK_STATE_NORMAL] = fg;
2663           rc_style->bg[GTK_STATE_NORMAL] = bg;
2664           rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
2665
2666           gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
2667           gtk_rc_style_unref (rc_style);
2668         }
2669     }
2670
2671   gtk_signal_connect (GTK_OBJECT (list), "select_child",
2672                       GTK_SIGNAL_FUNC (list_select_cb),
2673                       (gpointer) s);
2674   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
2675                       GTK_SIGNAL_FUNC (list_unselect_cb),
2676                       (gpointer) s);
2677 #endif /* !HAVE_GTK2 */
2678 }
2679
2680 static void
2681 update_list_sensitivity (state *s)
2682 {
2683   saver_preferences *p = &s->prefs;
2684   Bool sensitive = (p->mode == RANDOM_HACKS ||
2685                     p->mode == RANDOM_HACKS_SAME ||
2686                     p->mode == ONE_HACK);
2687   Bool checkable = (p->mode == RANDOM_HACKS ||
2688                     p->mode == RANDOM_HACKS_SAME);
2689   Bool blankable = (p->mode != DONT_BLANK);
2690
2691 #ifndef HAVE_GTK2
2692   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
2693   GtkWidget *use      = name_to_widget (s, "use_col_frame");
2694 #endif /* HAVE_GTK2 */
2695   GtkWidget *scroller = name_to_widget (s, "scroller");
2696   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
2697   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
2698
2699 #ifdef HAVE_GTK2
2700   GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
2701   GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
2702 #else /* !HAVE_GTK2 */
2703   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2704   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
2705
2706   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
2707 #endif /* !HAVE_GTK2 */
2708   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
2709   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
2710
2711   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
2712
2713 #ifdef HAVE_GTK2
2714   gtk_tree_view_column_set_visible (use, checkable);
2715 #else  /* !HAVE_GTK2 */
2716   if (checkable)
2717     gtk_widget_show (use);   /* the "Use" column header */
2718   else
2719     gtk_widget_hide (use);
2720
2721   while (kids)
2722     {
2723       GtkBin *line = GTK_BIN (kids->data);
2724       GtkContainer *line_hbox = GTK_CONTAINER (line->child);
2725       GtkWidget *line_check =
2726         GTK_WIDGET (gtk_container_children (line_hbox)->data);
2727       
2728       if (checkable)
2729         gtk_widget_show (line_check);
2730       else
2731         gtk_widget_hide (line_check);
2732
2733       kids = kids->next;
2734     }
2735 #endif /* !HAVE_GTK2 */
2736 }
2737
2738
2739 static void
2740 populate_prefs_page (state *s)
2741 {
2742   saver_preferences *p = &s->prefs;
2743
2744   Bool can_lock_p = True;
2745
2746   /* Disable all the "lock" controls if locking support was not provided
2747      at compile-time, or if running on MacOS. */
2748 # if defined(NO_LOCKING) || defined(__APPLE__)
2749   can_lock_p = False;
2750 # endif
2751
2752
2753   /* If there is only one screen, the mode menu contains
2754      "random" but not "random-same".
2755    */
2756   if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
2757     p->mode = RANDOM_HACKS;
2758
2759
2760   /* The file supports timeouts of less than a minute, but the GUI does
2761      not, so throttle the values to be at least one minute (since "0" is
2762      a bad rounding choice...)
2763    */
2764 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2765   THROTTLE (timeout);
2766   THROTTLE (cycle);
2767   /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
2768 # undef THROTTLE
2769
2770 # define FMT_MINUTES(NAME,N) \
2771     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2772
2773 # define FMT_SECONDS(NAME,N) \
2774     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2775
2776   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
2777   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
2778   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
2779   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2780   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2781   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
2782   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
2783
2784 # undef FMT_MINUTES
2785 # undef FMT_SECONDS
2786
2787 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2788   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2789                                 (ACTIVEP))
2790
2791   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
2792 #if 0
2793   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
2794   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
2795   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
2796 #endif
2797   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
2798   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
2799   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2800   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2801   TOGGLE_ACTIVE ("install_button",    p->install_cmap_p);
2802   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
2803   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
2804
2805   switch (p->tmode)
2806     {
2807     case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio",         True); break;
2808     case TEXT_FILE:    TOGGLE_ACTIVE ("text_file_radio",    True); break;
2809     case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
2810     case TEXT_URL:     TOGGLE_ACTIVE ("text_url_radio",     True); break;
2811     default:           TOGGLE_ACTIVE ("text_host_radio",    True); break;
2812     }
2813
2814 # undef TOGGLE_ACTIVE
2815
2816   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2817                       (p->image_directory ? p->image_directory : ""));
2818   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2819                             p->random_image_p);
2820   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2821                             p->random_image_p);
2822
2823   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
2824                       (p->text_literal ? p->text_literal : ""));
2825   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2826                       (p->text_file ? p->text_file : ""));
2827   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2828                       (p->text_program ? p->text_program : ""));
2829   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
2830                       (p->text_url ? p->text_url : ""));
2831
2832   gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
2833                             p->tmode == TEXT_LITERAL);
2834   gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
2835                             p->tmode == TEXT_FILE);
2836   gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
2837                             p->tmode == TEXT_FILE);
2838   gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
2839                             p->tmode == TEXT_PROGRAM);
2840   gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
2841                             p->tmode == TEXT_PROGRAM);
2842   gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
2843                             p->tmode == TEXT_URL);
2844
2845
2846   /* Map the `saver_mode' enum to mode menu to values. */
2847   {
2848     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2849
2850     int i;
2851     for (i = 0; i < countof(mode_menu_order); i++)
2852       if (mode_menu_order[i] == p->mode)
2853         break;
2854     gtk_option_menu_set_history (opt, i);
2855     update_list_sensitivity (s);
2856   }
2857
2858   {
2859     Bool found_any_writable_cells = False;
2860     Bool fading_possible = False;
2861     Bool dpms_supported = False;
2862
2863     Display *dpy = GDK_DISPLAY();
2864     int nscreens = ScreenCount(dpy);  /* real screens, not Xinerama */
2865     int i;
2866     for (i = 0; i < nscreens; i++)
2867       {
2868         Screen *s = ScreenOfDisplay (dpy, i);
2869         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2870           {
2871             found_any_writable_cells = True;
2872             break;
2873           }
2874       }
2875
2876     fading_possible = found_any_writable_cells;
2877 #ifdef HAVE_XF86VMODE_GAMMA
2878     fading_possible = True;
2879 #endif
2880
2881 #ifdef HAVE_DPMS_EXTENSION
2882     {
2883       int op = 0, event = 0, error = 0;
2884       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2885         dpms_supported = True;
2886     }
2887 #endif /* HAVE_DPMS_EXTENSION */
2888
2889
2890 # define SENSITIZE(NAME,SENSITIVEP) \
2891     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2892
2893     /* Blanking and Locking
2894      */
2895     SENSITIZE ("lock_button",     can_lock_p);
2896     SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
2897     SENSITIZE ("lock_mlabel",     can_lock_p && p->lock_p);
2898
2899     /* DPMS
2900      */
2901     SENSITIZE ("dpms_frame",              dpms_supported);
2902     SENSITIZE ("dpms_button",             dpms_supported);
2903     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
2904     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
2905     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2906     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
2907     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
2908     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2909     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
2910     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
2911     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
2912
2913     /* Colormaps
2914      */
2915     SENSITIZE ("cmap_frame",      found_any_writable_cells || fading_possible);
2916     SENSITIZE ("install_button",  found_any_writable_cells);
2917     SENSITIZE ("fade_button",     fading_possible);
2918     SENSITIZE ("unfade_button",   fading_possible);
2919
2920     SENSITIZE ("fade_label",      (fading_possible &&
2921                                    (p->fade_p || p->unfade_p)));
2922     SENSITIZE ("fade_spinbutton", (fading_possible &&
2923                                    (p->fade_p || p->unfade_p)));
2924
2925 # undef SENSITIZE
2926   }
2927 }
2928
2929
2930 static void
2931 populate_popup_window (state *s)
2932 {
2933   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2934   char *doc_string = 0;
2935
2936   /* #### not in Gtk 1.2
2937   gtk_label_set_selectable (doc);
2938    */
2939
2940 # ifdef HAVE_XML
2941   if (s->cdata)
2942     {
2943       free_conf_data (s->cdata);
2944       s->cdata = 0;
2945     }
2946
2947   {
2948     saver_preferences *p = &s->prefs;
2949     int list_elt = selected_list_element (s);
2950     int hack_number = (list_elt >= 0 && list_elt < s->list_count
2951                        ? s->list_elt_to_hack_number[list_elt]
2952                        : -1);
2953     screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2954     if (hack)
2955       {
2956         GtkWidget *parent = name_to_widget (s, "settings_vbox");
2957         GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2958         const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2959         s->cdata = load_configurator (cmd_line, s->debug_p);
2960         if (s->cdata && s->cdata->widget)
2961           gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
2962                               TRUE, TRUE, 0);
2963       }
2964   }
2965
2966   doc_string = (s->cdata
2967                 ? s->cdata->description
2968                 : 0);
2969 # else  /* !HAVE_XML */
2970   doc_string = _("Descriptions not available: no XML support compiled in.");
2971 # endif /* !HAVE_XML */
2972
2973   gtk_label_set_text (doc, (doc_string
2974                             ? _(doc_string)
2975                             : _("No description available.")));
2976 }
2977
2978
2979 static void
2980 sensitize_demo_widgets (state *s, Bool sensitive_p)
2981 {
2982   const char *names[] = { "demo", "settings",
2983                           "cmd_label", "cmd_text", "manual",
2984                           "visual", "visual_combo" };
2985   int i;
2986   for (i = 0; i < countof(names); i++)
2987     {
2988       GtkWidget *w = name_to_widget (s, names[i]);
2989       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2990     }
2991 }
2992
2993
2994 static void
2995 sensitize_menu_items (state *s, Bool force_p)
2996 {
2997   static Bool running_p = False;
2998   static time_t last_checked = 0;
2999   time_t now = time ((time_t *) 0);
3000   const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
3001                           /* "demo" */ };
3002   int i;
3003
3004   if (force_p || now > last_checked + 10)   /* check every 10 seconds */
3005     {
3006       running_p = xscreensaver_running_p (s);
3007       last_checked = time ((time_t *) 0);
3008     }
3009
3010   for (i = 0; i < countof(names); i++)
3011     {
3012       GtkWidget *w = name_to_widget (s, names[i]);
3013       gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
3014     }
3015 }
3016
3017
3018 /* When the File menu is de-posted after a "Restart Daemon" command,
3019    the window underneath doesn't repaint for some reason.  I guess this
3020    is a bug in exposure handling in GTK or GDK.  This works around it.
3021  */
3022 static void
3023 force_dialog_repaint (state *s)
3024 {
3025 #if 1
3026   /* Tell GDK to invalidate and repaint the whole window.
3027    */
3028   GdkWindow *w = GET_WINDOW (s->toplevel_widget);
3029   GdkRegion *region = gdk_region_new ();
3030   GdkRectangle rect;
3031   rect.x = rect.y = 0;
3032   rect.width = rect.height = 32767;
3033   gdk_region_union_with_rect (region, &rect);
3034   gdk_window_invalidate_region (w, region, True);
3035   gdk_region_destroy (region);
3036   gdk_window_process_updates (w, True);
3037 #else
3038   /* Force the server to send an exposure event by creating and then
3039      destroying a window as a child of the top level shell.
3040    */
3041   Display *dpy = GDK_DISPLAY();
3042   Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
3043   Window w;
3044   XWindowAttributes xgwa;
3045   XGetWindowAttributes (dpy, parent, &xgwa);
3046   w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
3047   XMapRaised (dpy, w);
3048   XDestroyWindow (dpy, w);
3049   XSync (dpy, False);
3050 #endif
3051 }
3052
3053
3054 /* Even though we've given these text fields a maximum number of characters,
3055    their default size is still about 30 characters wide -- so measure out
3056    a string in their font, and resize them to just fit that.
3057  */
3058 static void
3059 fix_text_entry_sizes (state *s)
3060 {
3061   GtkWidget *w;
3062
3063 # if 0   /* appears no longer necessary with Gtk 1.2.10 */
3064   const char * const spinbuttons[] = {
3065     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
3066     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
3067     "dpms_off_spinbutton",
3068     "-fade_spinbutton" };
3069   int i;
3070   int width = 0;
3071
3072   for (i = 0; i < countof(spinbuttons); i++)
3073     {
3074       const char *n = spinbuttons[i];
3075       int cols = 4;
3076       while (*n == '-') n++, cols--;
3077       w = GTK_WIDGET (name_to_widget (s, n));
3078       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
3079       gtk_widget_set_usize (w, width, -2);
3080     }
3081
3082   /* Now fix the width of the combo box.
3083    */
3084   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
3085   w = GTK_COMBO (w)->entry;
3086   width = gdk_string_width (w->style->font, "PseudoColor___");
3087   gtk_widget_set_usize (w, width, -2);
3088
3089   /* Now fix the width of the file entry text.
3090    */
3091   w = GTK_WIDGET (name_to_widget (s, "image_text"));
3092   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
3093   gtk_widget_set_usize (w, width, -2);
3094
3095   /* Now fix the width of the command line text.
3096    */
3097   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
3098   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
3099   gtk_widget_set_usize (w, width, -2);
3100
3101 # endif /* 0 */
3102
3103   /* Now fix the height of the list widget:
3104      make it default to being around 10 text-lines high instead of 4.
3105    */
3106   w = GTK_WIDGET (name_to_widget (s, "list"));
3107   {
3108     int lines = 10;
3109     int height;
3110     int leading = 3;  /* approximate is ok... */
3111     int border = 2;
3112
3113 #ifdef HAVE_GTK2
3114     PangoFontMetrics *pain =
3115       pango_context_get_metrics (gtk_widget_get_pango_context (w),
3116                                  gtk_widget_get_style (w)->font_desc,
3117                                  gtk_get_default_language ());
3118     height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
3119                            pango_font_metrics_get_descent (pain));
3120 #else  /* !HAVE_GTK2 */
3121     height = w->style->font->ascent + w->style->font->descent;
3122 #endif /* !HAVE_GTK2 */
3123
3124     height += leading;
3125     height *= lines;
3126     height += border * 2;
3127     w = GTK_WIDGET (name_to_widget (s, "scroller"));
3128     gtk_widget_set_usize (w, -2, height);
3129   }
3130 }
3131
3132
3133 #ifndef HAVE_GTK2
3134 \f
3135 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
3136  */
3137
3138 static char *up_arrow_xpm[] = {
3139   "15 15 4 1",
3140   "     c None",
3141   "-    c #FFFFFF",
3142   "+    c #D6D6D6",
3143   "@    c #000000",
3144
3145   "       @       ",
3146   "       @       ",
3147   "      -+@      ",
3148   "      -+@      ",
3149   "     -+++@     ",
3150   "     -+++@     ",
3151   "    -+++++@    ",
3152   "    -+++++@    ",
3153   "   -+++++++@   ",
3154   "   -+++++++@   ",
3155   "  -+++++++++@  ",
3156   "  -+++++++++@  ",
3157   " -+++++++++++@ ",
3158   " @@@@@@@@@@@@@ ",
3159   "               ",
3160
3161   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3162      the end of the array (Gtk 1.2.5.) */
3163   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3164   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3165 };
3166
3167 static char *down_arrow_xpm[] = {
3168   "15 15 4 1",
3169   "     c None",
3170   "-    c #FFFFFF",
3171   "+    c #D6D6D6",
3172   "@    c #000000",
3173
3174   "               ",
3175   " ------------- ",
3176   " -+++++++++++@ ",
3177   "  -+++++++++@  ",
3178   "  -+++++++++@  ",
3179   "   -+++++++@   ",
3180   "   -+++++++@   ",
3181   "    -+++++@    ",
3182   "    -+++++@    ",
3183   "     -+++@     ",
3184   "     -+++@     ",
3185   "      -+@      ",
3186   "      -+@      ",
3187   "       @       ",
3188   "       @       ",
3189
3190   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3191      the end of the array (Gtk 1.2.5.) */
3192   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3193   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3194 };
3195
3196 static void
3197 pixmapify_button (state *s, int down_p)
3198 {
3199   GdkPixmap *pixmap;
3200   GdkBitmap *mask;
3201   GtkWidget *pixmapwid;
3202   GtkStyle *style;
3203   GtkWidget *w;
3204
3205   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
3206   style = gtk_widget_get_style (w);
3207   mask = 0;
3208   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
3209                                          &style->bg[GTK_STATE_NORMAL],
3210                                          (down_p
3211                                           ? (gchar **) down_arrow_xpm
3212                                           : (gchar **) up_arrow_xpm));
3213   pixmapwid = gtk_pixmap_new (pixmap, mask);
3214   gtk_widget_show (pixmapwid);
3215   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
3216   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
3217 }
3218
3219 static void
3220 map_next_button_cb (GtkWidget *w, gpointer user_data)
3221 {
3222   state *s = (state *) user_data;
3223   pixmapify_button (s, 1);
3224 }
3225
3226 static void
3227 map_prev_button_cb (GtkWidget *w, gpointer user_data)
3228 {
3229   state *s = (state *) user_data;
3230   pixmapify_button (s, 0);
3231 }
3232 #endif /* !HAVE_GTK2 */
3233
3234 \f
3235 #ifndef HAVE_GTK2
3236 /* Work around a Gtk bug that causes label widgets to wrap text too early.
3237  */
3238
3239 static void
3240 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
3241                                              GtkAllocation *allocation,
3242                                              void *foo)
3243 {
3244   GtkRequisition req;
3245   GtkWidgetAuxInfo *aux_info;
3246
3247   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
3248
3249   aux_info->width = allocation->width;
3250   aux_info->height = -2;
3251   aux_info->x = -1;
3252   aux_info->y = -1;
3253
3254   gtk_widget_size_request (label, &req);
3255 }
3256
3257 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
3258  */
3259 static void
3260 eschew_gtk_lossage (GtkLabel *label)
3261 {
3262   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
3263   aux_info->width = GTK_WIDGET (label)->allocation.width;
3264   aux_info->height = -2;
3265   aux_info->x = -1;
3266   aux_info->y = -1;
3267
3268   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
3269
3270   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
3271                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
3272                       0);
3273
3274   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
3275
3276   gtk_widget_queue_resize (GTK_WIDGET (label));
3277 }
3278 #endif /* !HAVE_GTK2 */
3279
3280
3281 static void
3282 populate_demo_window (state *s, int list_elt)
3283 {
3284   Display *dpy = GDK_DISPLAY();
3285   saver_preferences *p = &s->prefs;
3286   screenhack *hack;
3287   char *pretty_name;
3288   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
3289   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "opt_frame"));
3290   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
3291   GtkCombo *vis    = GTK_COMBO (name_to_widget (s, "visual_combo"));
3292   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
3293
3294   if (p->mode == BLANK_ONLY)
3295     {
3296       hack = 0;
3297       pretty_name = strdup (_("Blank Screen"));
3298       schedule_preview (s, 0);
3299     }
3300   else if (p->mode == DONT_BLANK)
3301     {
3302       hack = 0;
3303       pretty_name = strdup (_("Screen Saver Disabled"));
3304       schedule_preview (s, 0);
3305     }
3306   else
3307     {
3308       int hack_number = (list_elt >= 0 && list_elt < s->list_count
3309                          ? s->list_elt_to_hack_number[list_elt]
3310                          : -1);
3311       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
3312
3313       pretty_name = (hack
3314                      ? (hack->name
3315                         ? strdup (hack->name)
3316                         : make_hack_name (dpy, hack->command))
3317                      : 0);
3318
3319       if (hack)
3320         schedule_preview (s, hack->command);
3321       else
3322         schedule_preview (s, 0);
3323     }
3324
3325   if (!pretty_name)
3326     pretty_name = strdup (_("Preview"));
3327
3328   gtk_frame_set_label (frame1, _(pretty_name));
3329   gtk_frame_set_label (frame2, _(pretty_name));
3330
3331   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
3332   gtk_entry_set_position (cmd, 0);
3333
3334   {
3335     char title[255];
3336     sprintf (title, _("%s: %.100s Settings"),
3337              progclass, (pretty_name ? pretty_name : "???"));
3338     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
3339   }
3340
3341   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
3342                       (hack
3343                        ? (hack->visual && *hack->visual
3344                           ? hack->visual
3345                           : _("Any"))
3346                        : ""));
3347
3348   sensitize_demo_widgets (s, (hack ? True : False));
3349
3350   if (pretty_name) free (pretty_name);
3351
3352   ensure_selected_item_visible (list);
3353
3354   s->_selected_list_element = list_elt;
3355 }
3356
3357
3358 static void
3359 widget_deleter (GtkWidget *widget, gpointer data)
3360 {
3361   /* #### Well, I want to destroy these widgets, but if I do that, they get
3362      referenced again, and eventually I get a SEGV.  So instead of
3363      destroying them, I'll just hide them, and leak a bunch of memory
3364      every time the disk file changes.  Go go go Gtk!
3365
3366      #### Ok, that's a lie, I get a crash even if I just hide the widget
3367      and don't ever delete it.  Fuck!
3368    */
3369 #if 0
3370   gtk_widget_destroy (widget);
3371 #else
3372   gtk_widget_hide (widget);
3373 #endif
3374 }
3375
3376
3377 static char **sort_hack_cmp_names_kludge;
3378 static int
3379 sort_hack_cmp (const void *a, const void *b)
3380 {
3381   if (a == b)
3382     return 0;
3383   else
3384     {
3385       int aa = *(int *) a;
3386       int bb = *(int *) b;
3387       const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
3388       return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
3389                      (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
3390     }
3391 }
3392
3393
3394 static void
3395 initialize_sort_map (state *s)
3396 {
3397   Display *dpy = GDK_DISPLAY();
3398   saver_preferences *p = &s->prefs;
3399   int i, j;
3400
3401   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
3402   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
3403   if (s->hacks_available_p) free (s->hacks_available_p);
3404
3405   s->list_elt_to_hack_number = (int *)
3406     calloc (sizeof(int), p->screenhacks_count + 1);
3407   s->hack_number_to_list_elt = (int *)
3408     calloc (sizeof(int), p->screenhacks_count + 1);
3409   s->hacks_available_p = (Bool *)
3410     calloc (sizeof(Bool), p->screenhacks_count + 1);
3411   s->total_available = 0;
3412
3413   /* Check which hacks actually exist on $PATH
3414    */
3415   for (i = 0; i < p->screenhacks_count; i++)
3416     {
3417       screenhack *hack = p->screenhacks[i];
3418       int on = on_path_p (hack->command) ? 1 : 0;
3419       s->hacks_available_p[i] = on;
3420       s->total_available += on;
3421     }
3422
3423   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
3424      hacks, if desired.
3425    */
3426   j = 0;
3427   for (i = 0; i < p->screenhacks_count; i++)
3428     {
3429       if (!p->ignore_uninstalled_p ||
3430           s->hacks_available_p[i])
3431         s->list_elt_to_hack_number[j++] = i;
3432     }
3433   s->list_count = j;
3434
3435   for (; j < p->screenhacks_count; j++)
3436     s->list_elt_to_hack_number[j] = -1;
3437
3438
3439   /* Generate list of sortable names (once)
3440    */
3441   sort_hack_cmp_names_kludge = (char **)
3442     calloc (sizeof(char *), p->screenhacks_count);
3443   for (i = 0; i < p->screenhacks_count; i++)
3444     {
3445       screenhack *hack = p->screenhacks[i];
3446       char *name = (hack->name && *hack->name
3447                     ? strdup (hack->name)
3448                     : make_hack_name (dpy, hack->command));
3449       char *str;
3450       for (str = name; *str; str++)
3451         *str = tolower(*str);
3452       sort_hack_cmp_names_kludge[i] = name;
3453     }
3454
3455   /* Sort list->hack map alphabetically
3456    */
3457   qsort (s->list_elt_to_hack_number,
3458          p->screenhacks_count,
3459          sizeof(*s->list_elt_to_hack_number),
3460          sort_hack_cmp);
3461
3462   /* Free names
3463    */
3464   for (i = 0; i < p->screenhacks_count; i++)
3465     free (sort_hack_cmp_names_kludge[i]);
3466   free (sort_hack_cmp_names_kludge);
3467   sort_hack_cmp_names_kludge = 0;
3468
3469   /* Build inverse table */
3470   for (i = 0; i < p->screenhacks_count; i++)
3471     {
3472       int n = s->list_elt_to_hack_number[i];
3473       if (n != -1)
3474         s->hack_number_to_list_elt[n] = i;
3475     }
3476 }
3477
3478
3479 static int
3480 maybe_reload_init_file (state *s)
3481 {
3482   Display *dpy = GDK_DISPLAY();
3483   saver_preferences *p = &s->prefs;
3484   int status = 0;
3485
3486   static Bool reentrant_lock = False;
3487   if (reentrant_lock) return 0;
3488   reentrant_lock = True;
3489
3490   if (init_file_changed_p (p))
3491     {
3492       const char *f = init_file_name();
3493       char *b;
3494       int list_elt;
3495       GtkWidget *list;
3496
3497       if (!f || !*f) return 0;
3498       b = (char *) malloc (strlen(f) + 1024);
3499       sprintf (b,
3500                _("Warning:\n\n"
3501                  "file \"%s\" has changed, reloading.\n"),
3502                f);
3503       warning_dialog (s->toplevel_widget, b, D_NONE, 100);
3504       free (b);
3505
3506       load_init_file (dpy, p);
3507       initialize_sort_map (s);
3508
3509       list_elt = selected_list_element (s);
3510       list = name_to_widget (s, "list");
3511       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
3512       populate_hack_list (s);
3513       force_list_select_item (s, list, list_elt, True);
3514       populate_prefs_page (s);
3515       populate_demo_window (s, list_elt);
3516       ensure_selected_item_visible (list);
3517
3518       status = 1;
3519     }
3520
3521   reentrant_lock = False;
3522   return status;
3523 }
3524
3525
3526 \f
3527 /* Making the preview window have the right X visual (so that GL works.)
3528  */
3529
3530 static Visual *get_best_gl_visual (state *);
3531
3532 static GdkVisual *
3533 x_visual_to_gdk_visual (Visual *xv)
3534 {
3535   GList *gvs = gdk_list_visuals();
3536   if (!xv) return gdk_visual_get_system();
3537   for (; gvs; gvs = gvs->next)
3538     {
3539       GdkVisual *gv = (GdkVisual *) gvs->data;
3540       if (xv == GDK_VISUAL_XVISUAL (gv))
3541         return gv;
3542     }
3543   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
3544            blurb(), (unsigned long) xv->visualid);
3545   abort();
3546 }
3547
3548 static void
3549 clear_preview_window (state *s)
3550 {
3551   GtkWidget *p;
3552   GdkWindow *window;
3553   GtkStyle  *style;
3554
3555   if (!s->toplevel_widget) return;  /* very early */
3556   p = name_to_widget (s, "preview");
3557   window = GET_WINDOW (p);
3558
3559   if (!window) return;
3560
3561   /* Flush the widget background down into the window, in case a subproc
3562      has changed it. */
3563   style = gtk_widget_get_style (p);
3564   gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
3565   gdk_window_clear (window);
3566
3567   {
3568     int list_elt = selected_list_element (s);
3569     int hack_number = (list_elt >= 0
3570                        ? s->list_elt_to_hack_number[list_elt]
3571                        : -1);
3572     Bool available_p = (hack_number >= 0
3573                         ? s->hacks_available_p [hack_number]
3574                         : True);
3575     Bool nothing_p = (s->total_available < 5);
3576
3577 #ifdef HAVE_GTK2
3578     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
3579     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
3580                            (s->running_preview_error_p
3581                             ? (available_p ? 1 :
3582                                nothing_p ? 3 : 2)
3583                             : 0));
3584 #else /* !HAVE_GTK2 */
3585     if (s->running_preview_error_p)
3586       {
3587         const char * const lines1[] = { N_("No Preview"), N_("Available") };
3588         const char * const lines2[] = { N_("Not"), N_("Installed") };
3589         int nlines = countof(lines1);
3590         int lh = p->style->font->ascent + p->style->font->descent;
3591         int y, i;
3592         gint w, h;
3593
3594         const char * const *lines = (available_p ? lines1 : lines2);
3595
3596         gdk_window_get_size (window, &w, &h);
3597         y = (h - (lh * nlines)) / 2;
3598         y += p->style->font->ascent;
3599         for (i = 0; i < nlines; i++)
3600           {
3601             int sw = gdk_string_width (p->style->font, _(lines[i]));
3602             int x = (w - sw) / 2;
3603             gdk_draw_string (window, p->style->font,
3604                              p->style->fg_gc[GTK_STATE_NORMAL],
3605                              x, y, _(lines[i]));
3606             y += lh;
3607           }
3608       }
3609 #endif /* !HAVE_GTK2 */
3610   }
3611
3612   gdk_flush ();
3613 }
3614
3615
3616 static void
3617 reset_preview_window (state *s)
3618 {
3619   /* On some systems (most recently, MacOS X) OpenGL programs get confused
3620      when you kill one and re-start another on the same window.  So maybe
3621      it's best to just always destroy and recreate the preview window
3622      when changing hacks, instead of always trying to reuse the same one?
3623    */
3624   GtkWidget *pr = name_to_widget (s, "preview");
3625   if (GET_REALIZED (pr))
3626     {
3627       GdkWindow *window = GET_WINDOW (pr);
3628       Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3629       Window id;
3630       gtk_widget_hide (pr);
3631       gtk_widget_unrealize (pr);
3632       gtk_widget_realize (pr);
3633       gtk_widget_show (pr);
3634       id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3635       if (s->debug_p)
3636         fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
3637                  (unsigned int) oid,
3638                  (unsigned int) id);
3639     }
3640 }
3641
3642
3643 static void
3644 fix_preview_visual (state *s)
3645 {
3646   GtkWidget *widget = name_to_widget (s, "preview");
3647   Visual *xvisual = get_best_gl_visual (s);
3648   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
3649   GdkVisual *dvisual = gdk_visual_get_system();
3650   GdkColormap *cmap = (visual == dvisual
3651                        ? gdk_colormap_get_system ()
3652                        : gdk_colormap_new (visual, False));
3653
3654   if (s->debug_p)
3655     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
3656              (visual == dvisual ? "default" : "non-default"),
3657              (xvisual ? (unsigned long) xvisual->visualid : 0L));
3658
3659   if (!GET_REALIZED (widget) ||
3660       gtk_widget_get_visual (widget) != visual)
3661     {
3662       gtk_widget_unrealize (widget);
3663       gtk_widget_set_visual (widget, visual);
3664       gtk_widget_set_colormap (widget, cmap);
3665       gtk_widget_realize (widget);
3666     }
3667
3668   /* Set the Widget colors to be white-on-black. */
3669   {
3670     GdkWindow *window = GET_WINDOW (widget);
3671     GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget));
3672     GdkColormap *cmap = gtk_widget_get_colormap (widget);
3673     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
3674     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
3675     GdkGC *fgc = gdk_gc_new(window);
3676     GdkGC *bgc = gdk_gc_new(window);
3677     if (!gdk_color_white (cmap, fg)) abort();
3678     if (!gdk_color_black (cmap, bg)) abort();
3679     gdk_gc_set_foreground (fgc, fg);
3680     gdk_gc_set_background (fgc, bg);
3681     gdk_gc_set_foreground (bgc, bg);
3682     gdk_gc_set_background (bgc, fg);
3683     style->fg_gc[GTK_STATE_NORMAL] = fgc;
3684     style->bg_gc[GTK_STATE_NORMAL] = fgc;
3685     gtk_widget_set_style (widget, style);
3686
3687     /* For debugging purposes, put a title on the window (so that
3688        it can be easily found in the output of "xwininfo -tree".)
3689      */
3690     gdk_window_set_title (window, "Preview");
3691   }
3692
3693   gtk_widget_show (widget);
3694 }
3695
3696 \f
3697 /* Subprocesses
3698  */
3699
3700 static char *
3701 subproc_pretty_name (state *s)
3702 {
3703   if (s->running_preview_cmd)
3704     {
3705       char *ps = strdup (s->running_preview_cmd);
3706       char *ss = strchr (ps, ' ');
3707       if (ss) *ss = 0;
3708       ss = strrchr (ps, '/');
3709       if (!ss)
3710         ss = ps;
3711       else
3712         {
3713           ss = strdup (ss+1);
3714           free (ps);
3715         }
3716       return ss;
3717     }
3718   else
3719     return strdup ("???");
3720 }
3721
3722
3723 static void
3724 reap_zombies (state *s)
3725 {
3726   int wait_status = 0;
3727   pid_t pid;
3728   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
3729     {
3730       if (s->debug_p)
3731         {
3732           if (pid == s->running_preview_pid)
3733             {
3734               char *ss = subproc_pretty_name (s);
3735               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
3736                        (unsigned long) pid, ss);
3737               free (ss);
3738             }
3739           else
3740             fprintf (stderr, "%s: pid %lu died\n", blurb(),
3741                      (unsigned long) pid);
3742         }
3743     }
3744 }
3745
3746
3747 /* Mostly lifted from driver/subprocs.c */
3748 static Visual *
3749 get_best_gl_visual (state *s)
3750 {
3751   Display *dpy = GDK_DISPLAY();
3752   pid_t forked;
3753   int fds [2];
3754   int in, out;
3755   char buf[1024];
3756
3757   char *av[10];
3758   int ac = 0;
3759
3760   av[ac++] = "xscreensaver-gl-helper";
3761   av[ac] = 0;
3762
3763   if (pipe (fds))
3764     {
3765       perror ("error creating pipe:");
3766       return 0;
3767     }
3768
3769   in = fds [0];
3770   out = fds [1];
3771
3772   switch ((int) (forked = fork ()))
3773     {
3774     case -1:
3775       {
3776         sprintf (buf, "%s: couldn't fork", blurb());
3777         perror (buf);
3778         exit (1);
3779       }
3780     case 0:
3781       {
3782         int stdout_fd = 1;
3783
3784         close (in);  /* don't need this one */
3785         close (ConnectionNumber (dpy));         /* close display fd */
3786
3787         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
3788           {
3789             perror ("could not dup() a new stdout:");
3790             return 0;
3791           }
3792
3793         execvp (av[0], av);                     /* shouldn't return. */
3794
3795         if (errno != ENOENT)
3796           {
3797             /* Ignore "no such file or directory" errors, unless verbose.
3798                Issue all other exec errors, though. */
3799             sprintf (buf, "%s: running %s", blurb(), av[0]);
3800             perror (buf);
3801           }
3802
3803         /* Note that one must use _exit() instead of exit() in procs forked
3804            off of Gtk programs -- Gtk installs an atexit handler that has a
3805            copy of the X connection (which we've already closed, for safety.)
3806            If one uses exit() instead of _exit(), then one sometimes gets a
3807            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3808         */
3809         _exit (1);                              /* exits fork */
3810         break;
3811       }
3812     default:
3813       {
3814         int result = 0;
3815         int wait_status = 0;
3816
3817         FILE *f = fdopen (in, "r");
3818         unsigned int v = 0;
3819         char c;
3820
3821         close (out);  /* don't need this one */
3822
3823         *buf = 0;
3824         if (!fgets (buf, sizeof(buf)-1, f))
3825           *buf = 0;
3826         fclose (f);
3827
3828         /* Wait for the child to die. */
3829         waitpid (-1, &wait_status, 0);
3830
3831         if (1 == sscanf (buf, "0x%x %c", &v, &c))
3832           result = (int) v;
3833
3834         if (result == 0)
3835           {
3836             if (s->debug_p)
3837               fprintf (stderr, "%s: %s did not report a GL visual!\n",
3838                        blurb(), av[0]);
3839             return 0;
3840           }
3841         else
3842           {
3843             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3844             if (s->debug_p)
3845               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3846                        blurb(), av[0], result);
3847             if (!v) abort();
3848             return v;
3849           }
3850       }
3851     }
3852
3853   abort();
3854 }
3855
3856
3857 static void
3858 kill_preview_subproc (state *s, Bool reset_p)
3859 {
3860   s->running_preview_error_p = False;
3861
3862   reap_zombies (s);
3863   clear_preview_window (s);
3864
3865   if (s->subproc_check_timer_id)
3866     {
3867       gtk_timeout_remove (s->subproc_check_timer_id);
3868       s->subproc_check_timer_id = 0;
3869       s->subproc_check_countdown = 0;
3870     }
3871
3872   if (s->running_preview_pid)
3873     {
3874       int status = kill (s->running_preview_pid, SIGTERM);
3875       char *ss = subproc_pretty_name (s);
3876
3877       if (status < 0)
3878         {
3879           if (errno == ESRCH)
3880             {
3881               if (s->debug_p)
3882                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3883                          blurb(), (unsigned long) s->running_preview_pid, ss);
3884             }
3885           else
3886             {
3887               char buf [1024];
3888               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3889                        blurb(), (unsigned long) s->running_preview_pid, ss);
3890               perror (buf);
3891             }
3892         }
3893       else {
3894         int endstatus;
3895         waitpid(s->running_preview_pid, &endstatus, 0);
3896         if (s->debug_p)
3897           fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3898                    (unsigned long) s->running_preview_pid, ss);
3899       }
3900
3901       free (ss);
3902       s->running_preview_pid = 0;
3903       if (s->running_preview_cmd) free (s->running_preview_cmd);
3904       s->running_preview_cmd = 0;
3905     }
3906
3907   reap_zombies (s);
3908
3909   if (reset_p)
3910     {
3911       reset_preview_window (s);
3912       clear_preview_window (s);
3913     }
3914 }
3915
3916
3917 /* Immediately and unconditionally launches the given process,
3918    after appending the -window-id option; sets running_preview_pid.
3919  */
3920 static void
3921 launch_preview_subproc (state *s)
3922 {
3923   saver_preferences *p = &s->prefs;
3924   Window id;
3925   char *new_cmd = 0;
3926   pid_t forked;
3927   const char *cmd = s->desired_preview_cmd;
3928
3929   GtkWidget *pr = name_to_widget (s, "preview");
3930   GdkWindow *window;
3931
3932   reset_preview_window (s);
3933
3934   window = GET_WINDOW (pr);
3935
3936   s->running_preview_error_p = False;
3937
3938   if (s->preview_suppressed_p)
3939     {
3940       kill_preview_subproc (s, False);
3941       goto DONE;
3942     }
3943
3944   new_cmd = malloc (strlen (cmd) + 40);
3945
3946   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3947   if (id == 0)
3948     {
3949       /* No window id?  No command to run. */
3950       free (new_cmd);
3951       new_cmd = 0;
3952     }
3953   else
3954     {
3955       strcpy (new_cmd, cmd);
3956       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
3957                (unsigned int) id);
3958     }
3959
3960   kill_preview_subproc (s, False);
3961   if (! new_cmd)
3962     {
3963       s->running_preview_error_p = True;
3964       clear_preview_window (s);
3965       goto DONE;
3966     }
3967
3968   switch ((int) (forked = fork ()))
3969     {
3970     case -1:
3971       {
3972         char buf[255];
3973         sprintf (buf, "%s: couldn't fork", blurb());
3974         perror (buf);
3975         s->running_preview_error_p = True;
3976         goto DONE;
3977         break;
3978       }
3979     case 0:
3980       {
3981         close (ConnectionNumber (GDK_DISPLAY()));
3982
3983         hack_subproc_environment (id, s->debug_p);
3984
3985         usleep (250000);  /* pause for 1/4th second before launching, to give
3986                              the previous program time to die and flush its X
3987                              buffer, so we don't get leftover turds on the
3988                              window. */
3989
3990         exec_command (p->shell, new_cmd, p->nice_inferior);
3991         /* Don't bother printing an error message when we are unable to
3992            exec subprocesses; we handle that by polling the pid later.
3993
3994            Note that one must use _exit() instead of exit() in procs forked
3995            off of Gtk programs -- Gtk installs an atexit handler that has a
3996            copy of the X connection (which we've already closed, for safety.)
3997            If one uses exit() instead of _exit(), then one sometimes gets a
3998            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3999         */
4000         _exit (1);  /* exits child fork */
4001         break;
4002
4003       default:
4004
4005         if (s->running_preview_cmd) free (s->running_preview_cmd);
4006         s->running_preview_cmd = strdup (s->desired_preview_cmd);
4007         s->running_preview_pid = forked;
4008
4009         if (s->debug_p)
4010           {
4011             char *ss = subproc_pretty_name (s);
4012             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
4013                      (unsigned long) forked, ss);
4014             free (ss);
4015           }
4016         break;
4017       }
4018     }
4019
4020   schedule_preview_check (s);
4021
4022  DONE:
4023   if (new_cmd) free (new_cmd);
4024   new_cmd = 0;
4025 }
4026
4027
4028 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
4029  */
4030 static void
4031 hack_environment (state *s)
4032 {
4033   static const char *def_path =
4034 # ifdef DEFAULT_PATH_PREFIX
4035     DEFAULT_PATH_PREFIX;
4036 # else
4037     "";
4038 # endif
4039
4040   Display *dpy = GDK_DISPLAY();
4041   const char *odpy = DisplayString (dpy);
4042   char *ndpy = (char *) malloc(strlen(odpy) + 20);
4043   strcpy (ndpy, "DISPLAY=");
4044   strcat (ndpy, odpy);
4045   if (putenv (ndpy))
4046     abort ();
4047
4048   if (s->debug_p)
4049     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
4050
4051   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
4052      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
4053      So we must leak it (and/or the previous setting).  Yay.
4054    */
4055
4056   if (def_path && *def_path)
4057     {
4058       const char *opath = getenv("PATH");
4059       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
4060       strcpy (npath, "PATH=");
4061       strcat (npath, def_path);
4062       strcat (npath, ":");
4063       strcat (npath, opath);
4064
4065       if (putenv (npath))
4066         abort ();
4067       /* do not free(npath) -- see above */
4068
4069       if (s->debug_p)
4070         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
4071     }
4072 }
4073
4074
4075 static void
4076 hack_subproc_environment (Window preview_window_id, Bool debug_p)
4077 {
4078   /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
4079      necessary yet, but it will make programs work if we had invoked
4080      them with "-root" and not with "-window-id" -- which, of course,
4081      doesn't happen.
4082    */
4083   char *nssw = (char *) malloc (40);
4084   sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
4085
4086   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
4087      any more, right?  It's not Posix, but everyone seems to have it. */
4088   if (putenv (nssw))
4089     abort ();
4090
4091   if (debug_p)
4092     fprintf (stderr, "%s: %s\n", blurb(), nssw);
4093
4094   /* do not free(nssw) -- see above */
4095 }
4096
4097
4098 /* Called from a timer:
4099    Launches the currently-chosen subprocess, if it's not already running.
4100    If there's a different process running, kills it.
4101  */
4102 static int
4103 update_subproc_timer (gpointer data)
4104 {
4105   state *s = (state *) data;
4106   if (! s->desired_preview_cmd)
4107     kill_preview_subproc (s, True);
4108   else if (!s->running_preview_cmd ||
4109            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
4110     launch_preview_subproc (s);
4111
4112   s->subproc_timer_id = 0;
4113   return FALSE;  /* do not re-execute timer */
4114 }
4115
4116 static int
4117 settings_timer (gpointer data)
4118 {
4119   settings_cb (0, 0);
4120   return FALSE;
4121 }
4122
4123
4124 /* Call this when you think you might want a preview process running.
4125    It will set a timer that will actually launch that program a second
4126    from now, if you haven't changed your mind (to avoid double-click
4127    spazzing, etc.)  `cmd' may be null meaning "no process".
4128  */
4129 static void
4130 schedule_preview (state *s, const char *cmd)
4131 {
4132   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
4133
4134   if (s->debug_p)
4135     {
4136       if (cmd)
4137         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
4138       else
4139         fprintf (stderr, "%s: scheduling preview death\n", blurb());
4140     }
4141
4142   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
4143   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
4144
4145   if (s->subproc_timer_id)
4146     gtk_timeout_remove (s->subproc_timer_id);
4147   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
4148 }
4149
4150
4151 /* Called from a timer:
4152    Checks to see if the subproc that should be running, actually is.
4153  */
4154 static int
4155 check_subproc_timer (gpointer data)
4156 {
4157   state *s = (state *) data;
4158   Bool again_p = True;
4159
4160   if (s->running_preview_error_p ||   /* already dead */
4161       s->running_preview_pid <= 0)
4162     {
4163       again_p = False;
4164     }
4165   else
4166     {
4167       int status;
4168       reap_zombies (s);
4169       status = kill (s->running_preview_pid, 0);
4170       if (status < 0 && errno == ESRCH)
4171         s->running_preview_error_p = True;
4172
4173       if (s->debug_p)
4174         {
4175           char *ss = subproc_pretty_name (s);
4176           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
4177                    (unsigned long) s->running_preview_pid, ss,
4178                    (s->running_preview_error_p ? "dead" : "alive"));
4179           free (ss);
4180         }
4181
4182       if (s->running_preview_error_p)
4183         {
4184           clear_preview_window (s);
4185           again_p = False;
4186         }
4187     }
4188
4189   /* Otherwise, it's currently alive.  We might be checking again, or we
4190      might be satisfied. */
4191
4192   if (--s->subproc_check_countdown <= 0)
4193     again_p = False;
4194
4195   if (again_p)
4196     return TRUE;     /* re-execute timer */
4197   else
4198     {
4199       s->subproc_check_timer_id = 0;
4200       s->subproc_check_countdown = 0;
4201       return FALSE;  /* do not re-execute timer */
4202     }
4203 }
4204
4205
4206 /* Call this just after launching a subprocess.
4207    This sets a timer that will, five times a second for two seconds,
4208    check whether the program is still running.  The assumption here
4209    is that if the process didn't stay up for more than a couple of
4210    seconds, then either the program doesn't exist, or it doesn't
4211    take a -window-id argument.
4212  */
4213 static void
4214 schedule_preview_check (state *s)
4215 {
4216   int seconds = 2;
4217   int ticks = 5;
4218
4219   if (s->debug_p)
4220     fprintf (stderr, "%s: scheduling check\n", blurb());
4221
4222   if (s->subproc_check_timer_id)
4223     gtk_timeout_remove (s->subproc_check_timer_id);
4224   s->subproc_check_timer_id =
4225     gtk_timeout_add (1000 / ticks,
4226                      check_subproc_timer, (gpointer) s);
4227   s->subproc_check_countdown = ticks * seconds;
4228 }
4229
4230
4231 static Bool
4232 screen_blanked_p (void)
4233 {
4234   Atom type;
4235   int format;
4236   unsigned long nitems, bytesafter;
4237   unsigned char *dataP = 0;
4238   Display *dpy = GDK_DISPLAY();
4239   Bool blanked_p = False;
4240
4241   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
4242                           XA_SCREENSAVER_STATUS,
4243                           0, 3, False, XA_INTEGER,
4244                           &type, &format, &nitems, &bytesafter,
4245                           &dataP)
4246       == Success
4247       && type == XA_INTEGER
4248       && nitems >= 3
4249       && dataP)
4250     {
4251       Atom *data = (Atom *) dataP;
4252       blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
4253     }
4254
4255   if (dataP) XFree (dataP);
4256
4257   return blanked_p;
4258 }
4259
4260 /* Wake up every now and then and see if the screen is blanked.
4261    If it is, kill off the small-window demo -- no point in wasting
4262    cycles by running two screensavers at once...
4263  */
4264 static int
4265 check_blanked_timer (gpointer data)
4266 {
4267   state *s = (state *) data;
4268   Bool blanked_p = screen_blanked_p ();
4269   if (blanked_p && s->running_preview_pid)
4270     {
4271       if (s->debug_p)
4272         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
4273       kill_preview_subproc (s, True);
4274     }
4275
4276   return True;  /* re-execute timer */
4277 }
4278
4279
4280 /* How many screens are there (including Xinerama.)
4281  */
4282 static int
4283 screen_count (Display *dpy)
4284 {
4285   int nscreens = ScreenCount(dpy);
4286 # ifdef HAVE_XINERAMA
4287   if (nscreens <= 1)
4288     {
4289       int event_number, error_number;
4290       if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
4291           XineramaIsActive (dpy))
4292         {
4293           XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
4294           if (xsi) XFree (xsi);
4295         }
4296     }
4297 # endif /* HAVE_XINERAMA */
4298
4299   return nscreens;
4300 }
4301
4302 \f
4303 /* Setting window manager icon
4304  */
4305
4306 static void
4307 init_icon (GdkWindow *window)
4308 {
4309   GdkBitmap *mask = 0;
4310   GdkColor transp;
4311   GdkPixmap *pixmap =
4312     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
4313                                   (gchar **) logo_50_xpm);
4314   if (pixmap)
4315     gdk_window_set_icon (window, 0, pixmap, mask);
4316 }
4317
4318 \f
4319 /* The main demo-mode command loop.
4320  */
4321
4322 #if 0
4323 static Bool
4324 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
4325         XrmRepresentation *type, XrmValue *value, XPointer closure)
4326 {
4327   int i;
4328   for (i = 0; quarks[i]; i++)
4329     {
4330       if (bindings[i] == XrmBindTightly)
4331         fprintf (stderr, (i == 0 ? "" : "."));
4332       else if (bindings[i] == XrmBindLoosely)
4333         fprintf (stderr, "*");
4334       else
4335         fprintf (stderr, " ??? ");
4336       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
4337     }
4338
4339   fprintf (stderr, ": %s\n", (char *) value->addr);
4340
4341   return False;
4342 }
4343 #endif
4344
4345
4346 static Window
4347 gnome_screensaver_window (Screen *screen)
4348 {
4349   Display *dpy = DisplayOfScreen (screen);
4350   Window root = RootWindowOfScreen (screen);
4351   Window parent, *kids;
4352   unsigned int nkids;
4353   Window gnome_window = 0;
4354   int i;
4355
4356   if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids))
4357     abort ();
4358   for (i = 0; i < nkids; i++)
4359     {
4360       Atom type;
4361       int format;
4362       unsigned long nitems, bytesafter;
4363       unsigned char *name;
4364       if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
4365                               False, XA_STRING, &type, &format, &nitems,
4366                               &bytesafter, &name)
4367           == Success
4368           && type != None
4369           && !strcmp ((char *) name, "gnome-screensaver"))
4370         {
4371           gnome_window = kids[i];
4372           break;
4373         }
4374     }
4375
4376   if (kids) XFree ((char *) kids);
4377   return gnome_window;
4378 }
4379
4380 static Bool
4381 gnome_screensaver_active_p (void)
4382 {
4383   Display *dpy = GDK_DISPLAY();
4384   Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy));
4385   return (w ? True : False);
4386 }
4387
4388 static void
4389 kill_gnome_screensaver (void)
4390 {
4391   Display *dpy = GDK_DISPLAY();
4392   Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy));
4393   if (w) XKillClient (dpy, (XID) w);
4394 }
4395
4396 static Bool
4397 kde_screensaver_active_p (void)
4398 {
4399   FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null",
4400                    "r");
4401   char buf[255];
4402   fgets (buf, sizeof(buf)-1, p);
4403   pclose (p);
4404   if (!strcmp (buf, "true\n"))
4405     return True;
4406   else
4407     return False;
4408 }
4409
4410 static void
4411 kill_kde_screensaver (void)
4412 {
4413   system ("dcop kdesktop KScreensaverIface enable false");
4414 }
4415
4416
4417 static void
4418 the_network_is_not_the_computer (state *s)
4419 {
4420   Display *dpy = GDK_DISPLAY();
4421   char *rversion = 0, *ruser = 0, *rhost = 0;
4422   char *luser, *lhost;
4423   char *msg = 0;
4424   struct passwd *p = getpwuid (getuid ());
4425   const char *d = DisplayString (dpy);
4426
4427 # if defined(HAVE_UNAME)
4428   struct utsname uts;
4429   if (uname (&uts) < 0)
4430     lhost = "<UNKNOWN>";
4431   else
4432     lhost = uts.nodename;
4433 # elif defined(VMS)
4434   strcpy (lhost, getenv("SYS$NODE"));
4435 # else  /* !HAVE_UNAME && !VMS */
4436   strcat (lhost, "<UNKNOWN>");
4437 # endif /* !HAVE_UNAME && !VMS */
4438
4439   if (p && p->pw_name)
4440     luser = p->pw_name;
4441   else
4442     luser = "???";
4443
4444   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
4445
4446   /* Make a buffer that's big enough for a number of copies of all the
4447      strings, plus some. */
4448   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
4449                                (ruser ? strlen(ruser) : 0) +
4450                                (rhost ? strlen(rhost) : 0) +
4451                                strlen(lhost) +
4452                                strlen(luser) +
4453                                strlen(d) +
4454                                1024));
4455   *msg = 0;
4456
4457   if (!rversion || !*rversion)
4458     {
4459       sprintf (msg,
4460                _("Warning:\n\n"
4461                  "The XScreenSaver daemon doesn't seem to be running\n"
4462                  "on display \"%s\".  Launch it now?"),
4463                d);
4464     }
4465   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
4466     {
4467       /* Warn that the two processes are running as different users.
4468        */
4469       sprintf(msg,
4470             _("Warning:\n\n"
4471               "%s is running as user \"%s\" on host \"%s\".\n"
4472               "But the xscreensaver managing display \"%s\"\n"
4473               "is running as user \"%s\" on host \"%s\".\n"
4474               "\n"
4475               "Since they are different users, they won't be reading/writing\n"
4476               "the same ~/.xscreensaver file, so %s isn't\n"
4477               "going to work right.\n"
4478               "\n"
4479               "You should either re-run %s as \"%s\", or re-run\n"
4480               "xscreensaver as \"%s\".\n"
4481               "\n"
4482               "Restart the xscreensaver daemon now?\n"),
4483               progname, luser, lhost,
4484               d,
4485               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4486               progname,
4487               progname, (ruser ? ruser : "???"),
4488               luser);
4489     }
4490   else if (rhost && *rhost && !!strcmp (rhost, lhost))
4491     {
4492       /* Warn that the two processes are running on different hosts.
4493        */
4494       sprintf (msg,
4495               _("Warning:\n\n"
4496                "%s is running as user \"%s\" on host \"%s\".\n"
4497                "But the xscreensaver managing display \"%s\"\n"
4498                "is running as user \"%s\" on host \"%s\".\n"
4499                "\n"
4500                "If those two machines don't share a file system (that is,\n"
4501                "if they don't see the same ~%s/.xscreensaver file) then\n"
4502                "%s won't work right.\n"
4503                "\n"
4504                "Restart the daemon on \"%s\" as \"%s\" now?\n"),
4505                progname, luser, lhost,
4506                d,
4507                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4508                luser,
4509                progname,
4510                lhost, luser);
4511     }
4512   else if (!!strcmp (rversion, s->short_version))
4513     {
4514       /* Warn that the version numbers don't match.
4515        */
4516       sprintf (msg,
4517              _("Warning:\n\n"
4518                "This is %s version %s.\n"
4519                "But the xscreensaver managing display \"%s\"\n"
4520                "is version %s.  This could cause problems.\n"
4521                "\n"
4522                "Restart the xscreensaver daemon now?\n"),
4523                progname, s->short_version,
4524                d,
4525                rversion);
4526     }
4527
4528
4529   if (*msg)
4530     warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
4531
4532   if (rversion) free (rversion);
4533   if (ruser) free (ruser);
4534   if (rhost) free (rhost);
4535   free (msg);
4536   msg = 0;
4537
4538   /* Note: since these dialogs are not modal, they will stack up.
4539      So we do this check *after* popping up the "xscreensaver is not
4540      running" dialog so that these are on top.  Good enough.
4541    */
4542
4543   if (gnome_screensaver_active_p ())
4544     warning_dialog (s->toplevel_widget,
4545                     _("Warning:\n\n"
4546                       "The GNOME screensaver daemon appears to be running.\n"
4547                       "It must be stopped for XScreenSaver to work properly.\n"
4548                       "\n"
4549                       "Stop the GNOME screen saver daemon now?\n"),
4550                     D_GNOME, 1);
4551
4552   if (kde_screensaver_active_p ())
4553     warning_dialog (s->toplevel_widget,
4554                     _("Warning:\n\n"
4555                       "The KDE screen saver daemon appears to be running.\n"
4556                       "It must be stopped for XScreenSaver to work properly.\n"
4557                       "\n"
4558                       "Stop the KDE screen saver daemon now?\n"),
4559                     D_KDE, 1);
4560 }
4561
4562
4563 /* We use this error handler so that X errors are preceeded by the name
4564    of the program that generated them.
4565  */
4566 static int
4567 demo_ehandler (Display *dpy, XErrorEvent *error)
4568 {
4569   state *s = global_state_kludge;  /* I hate C so much... */
4570   fprintf (stderr, "\nX error in %s:\n", blurb());
4571   XmuPrintDefaultErrorMessage (dpy, error, stderr);
4572   kill_preview_subproc (s, False);
4573   exit (-1);
4574   return 0;
4575 }
4576
4577
4578 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
4579    of the program that generated them; and also that we can ignore one
4580    particular bogus error message that Gdk madly spews.
4581  */
4582 static void
4583 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
4584                const gchar *message, gpointer user_data)
4585 {
4586   /* Ignore the message "Got event for unknown window: 0x...".
4587      Apparently some events are coming in for the xscreensaver window
4588      (presumably reply events related to the ClientMessage) and Gdk
4589      feels the need to complain about them.  So, just suppress any
4590      messages that look like that one.
4591    */
4592   if (strstr (message, "unknown window"))
4593     return;
4594
4595   fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
4596            (log_domain ? log_domain : progclass),
4597            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
4598             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
4599             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
4600             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
4601             log_level == G_LOG_LEVEL_INFO     ? "info" :
4602             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
4603            message,
4604            ((!*message || message[strlen(message)-1] != '\n')
4605             ? "\n" : ""));
4606 }
4607
4608
4609 #ifdef __GNUC__
4610  __extension__     /* shut up about "string length is greater than the length
4611                       ISO C89 compilers are required to support" when including
4612                       the .ad file... */
4613 #endif
4614
4615 STFU
4616 static char *defaults[] = {
4617 #include "XScreenSaver_ad.h"
4618  0
4619 };
4620
4621 #if 0
4622 #ifdef HAVE_CRAPPLET
4623 static struct poptOption crapplet_options[] = {
4624   {NULL, '\0', 0, NULL, 0}
4625 };
4626 #endif /* HAVE_CRAPPLET */
4627 #endif /* 0 */
4628
4629 const char *usage = "[--display dpy] [--prefs | --settings]"
4630 # ifdef HAVE_CRAPPLET
4631                     " [--crapplet]"
4632 # endif
4633             "\n\t\t   [--debug] [--sync] [--no-xshm] [--configdir dir]";
4634
4635 static void
4636 map_popup_window_cb (GtkWidget *w, gpointer user_data)
4637 {
4638   state *s = (state *) user_data;
4639   Boolean oi = s->initializing_p;
4640 #ifndef HAVE_GTK2
4641   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
4642 #endif
4643   s->initializing_p = True;
4644 #ifndef HAVE_GTK2
4645   eschew_gtk_lossage (label);
4646 #endif
4647   s->initializing_p = oi;
4648 }
4649
4650
4651 #if 0
4652 static void
4653 print_widget_tree (GtkWidget *w, int depth)
4654 {
4655   int i;
4656   for (i = 0; i < depth; i++)
4657     fprintf (stderr, "  ");
4658   fprintf (stderr, "%s\n", gtk_widget_get_name (w));
4659
4660   if (GTK_IS_LIST (w))
4661     {
4662       for (i = 0; i < depth+1; i++)
4663         fprintf (stderr, "  ");
4664       fprintf (stderr, "...list kids...\n");
4665     }
4666   else if (GTK_IS_CONTAINER (w))
4667     {
4668       GList *kids = gtk_container_children (GTK_CONTAINER (w));
4669       while (kids)
4670         {
4671           print_widget_tree (GTK_WIDGET (kids->data), depth+1);
4672           kids = kids->next;
4673         }
4674     }
4675 }
4676 #endif /* 0 */
4677
4678 static int
4679 delayed_scroll_kludge (gpointer data)
4680 {
4681   state *s = (state *) data;
4682   GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
4683   ensure_selected_item_visible (w);
4684
4685   /* Oh, this is just fucking lovely, too. */
4686   w = GTK_WIDGET (name_to_widget (s, "preview"));
4687   gtk_widget_hide (w);
4688   gtk_widget_show (w);
4689
4690   return FALSE;  /* do not re-execute timer */
4691 }
4692
4693 #ifdef HAVE_GTK2
4694
4695 GtkWidget *
4696 create_xscreensaver_demo (void)
4697 {
4698   GtkWidget *nb;
4699
4700   nb = name_to_widget (global_state_kludge, "preview_notebook");
4701   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
4702
4703   return name_to_widget (global_state_kludge, "xscreensaver_demo");
4704 }
4705
4706 GtkWidget *
4707 create_xscreensaver_settings_dialog (void)
4708 {
4709   GtkWidget *w, *box;
4710
4711   box = name_to_widget (global_state_kludge, "dialog_action_area");
4712
4713   w = name_to_widget (global_state_kludge, "adv_button");
4714   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4715
4716   w = name_to_widget (global_state_kludge, "std_button");
4717   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4718
4719   return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
4720 }
4721
4722 #endif /* HAVE_GTK2 */
4723
4724 int
4725 main (int argc, char **argv)
4726 {
4727   XtAppContext app;
4728   state S, *s;
4729   saver_preferences *p;
4730   Bool prefs_p = False;
4731   Bool settings_p = False;
4732   int i;
4733   Display *dpy;
4734   Widget toplevel_shell;
4735   char *real_progname = argv[0];
4736   char *window_title;
4737   char *geom = 0;
4738   Bool crapplet_p = False;
4739   char *str;
4740
4741 #ifdef ENABLE_NLS
4742   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
4743   textdomain (GETTEXT_PACKAGE);
4744
4745 # ifdef HAVE_GTK2
4746   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
4747 # else  /* !HAVE_GTK2 */
4748   if (!setlocale (LC_ALL, ""))
4749     fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
4750 # endif /* !HAVE_GTK2 */
4751
4752 #endif /* ENABLE_NLS */
4753
4754   str = strrchr (real_progname, '/');
4755   if (str) real_progname = str+1;
4756
4757   s = &S;
4758   memset (s, 0, sizeof(*s));
4759   s->initializing_p = True;
4760   p = &s->prefs;
4761
4762   global_state_kludge = s;  /* I hate C so much... */
4763
4764   progname = real_progname;
4765
4766   s->short_version = (char *) malloc (5);
4767   memcpy (s->short_version, screensaver_id + 17, 4);
4768   s->short_version [4] = 0;
4769
4770
4771   /* Register our error message logger for every ``log domain'' known.
4772      There's no way to do this globally, so I grepped the Gtk/Gdk sources
4773      for all of the domains that seem to be in use.
4774   */
4775   {
4776     const char * const domains[] = { 0,
4777                                      "Gtk", "Gdk", "GLib", "GModule",
4778                                      "GThread", "Gnome", "GnomeUI" };
4779     for (i = 0; i < countof(domains); i++)
4780       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
4781   }
4782
4783 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
4784 # ifndef HAVE_GTK2
4785   {
4786     const char *dir = DEFAULT_ICONDIR;
4787     if (*dir) add_pixmap_directory (dir);
4788   }
4789 # endif /* !HAVE_GTK2 */
4790 #endif /* DEFAULT_ICONDIR */
4791
4792   /* This is gross, but Gtk understands --display and not -display...
4793    */
4794   for (i = 1; i < argc; i++)
4795     if (argv[i][0] && argv[i][1] && 
4796         !strncmp(argv[i], "-display", strlen(argv[i])))
4797       argv[i] = "--display";
4798
4799
4800   /* We need to parse this arg really early... Sigh. */
4801   for (i = 1; i < argc; i++)
4802     {
4803       if (argv[i] &&
4804           (!strcmp(argv[i], "--crapplet") ||
4805            !strcmp(argv[i], "--capplet")))
4806         {
4807 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
4808           int j;
4809           crapplet_p = True;
4810           for (j = i; j < argc; j++)  /* remove it from the list */
4811             argv[j] = argv[j+1];
4812           argc--;
4813 # else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4814           fprintf (stderr, "%s: not compiled with --crapplet support\n",
4815                    real_progname);
4816           fprintf (stderr, "%s: %s\n", real_progname, usage);
4817           exit (1);
4818 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4819         }
4820       else if (argv[i] &&
4821                (!strcmp(argv[i], "--debug") ||
4822                 !strcmp(argv[i], "-debug") ||
4823                 !strcmp(argv[i], "-d")))
4824         {
4825           int j;
4826           s->debug_p = True;
4827           for (j = i; j < argc; j++)  /* remove it from the list */
4828             argv[j] = argv[j+1];
4829           argc--;
4830           i--;
4831         }
4832       else if (argv[i] &&
4833                argc > i+1 &&
4834                *argv[i+1] &&
4835                (!strcmp(argv[i], "-geometry") ||
4836                 !strcmp(argv[i], "-geom") ||
4837                 !strcmp(argv[i], "-geo") ||
4838                 !strcmp(argv[i], "-g")))
4839         {
4840           int j;
4841           geom = argv[i+1];
4842           for (j = i; j < argc; j++)  /* remove them from the list */
4843             argv[j] = argv[j+2];
4844           argc -= 2;
4845           i -= 2;
4846         }
4847       else if (argv[i] &&
4848                argc > i+1 &&
4849                *argv[i+1] &&
4850                (!strcmp(argv[i], "--configdir")))
4851         {
4852           int j;
4853           struct stat st;
4854           hack_configuration_path = argv[i+1];
4855           for (j = i; j < argc; j++)  /* remove them from the list */
4856             argv[j] = argv[j+2];
4857           argc -= 2;
4858           i -= 2;
4859
4860           if (0 != stat (hack_configuration_path, &st))
4861             {
4862               char buf[255];
4863               sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
4864               perror (buf);
4865               exit (1);
4866             }
4867           else if (!S_ISDIR (st.st_mode))
4868             {
4869               fprintf (stderr, "%s: not a directory: %s\n",
4870                        blurb(), hack_configuration_path);
4871               exit (1);
4872             }
4873         }
4874     }
4875
4876
4877   if (s->debug_p)
4878     fprintf (stderr, "%s: using config directory \"%s\"\n",
4879              progname, hack_configuration_path);
4880
4881
4882   /* Let Gtk open the X connection, then initialize Xt to use that
4883      same connection.  Doctor Frankenstein would be proud.
4884    */
4885 # ifdef HAVE_CRAPPLET
4886   if (crapplet_p)
4887     {
4888       GnomeClient *client;
4889       GnomeClientFlags flags = 0;
4890
4891       int init_results = gnome_capplet_init ("screensaver-properties",
4892                                              s->short_version,
4893                                              argc, argv, NULL, 0, NULL);
4894       /* init_results is:
4895          0 upon successful initialization;
4896          1 if --init-session-settings was passed on the cmdline;
4897          2 if --ignore was passed on the cmdline;
4898         -1 on error.
4899
4900          So the 1 signifies just to init the settings, and quit, basically.
4901          (Meaning launch the xscreensaver daemon.)
4902        */
4903
4904       if (init_results < 0)
4905         {
4906 #  if 0
4907           g_error ("An initialization error occurred while "
4908                    "starting xscreensaver-capplet.\n");
4909 #  else  /* !0 */
4910           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
4911                    real_progname, init_results);
4912           exit (1);
4913 #  endif /* !0 */
4914         }
4915
4916       client = gnome_master_client ();
4917
4918       if (client)
4919         flags = gnome_client_get_flags (client);
4920
4921       if (flags & GNOME_CLIENT_IS_CONNECTED)
4922         {
4923           int token =
4924             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
4925                                          gnome_client_get_id (client));
4926           if (token)
4927             {
4928               char *session_args[20];
4929               int i = 0;
4930               session_args[i++] = real_progname;
4931               session_args[i++] = "--capplet";
4932               session_args[i++] = "--init-session-settings";
4933               session_args[i] = 0;
4934               gnome_client_set_priority (client, 20);
4935               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
4936               gnome_client_set_restart_command (client, i, session_args);
4937             }
4938           else
4939             {
4940               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
4941             }
4942
4943           gnome_client_flush (client);
4944         }
4945
4946       if (init_results == 1)
4947         {
4948           system ("xscreensaver -nosplash &");
4949           return 0;
4950         }
4951
4952     }
4953   else
4954 # endif /* HAVE_CRAPPLET */
4955     {
4956       gtk_init (&argc, &argv);
4957     }
4958
4959
4960   /* We must read exactly the same resources as xscreensaver.
4961      That means we must have both the same progclass *and* progname,
4962      at least as far as the resource database is concerned.  So,
4963      put "xscreensaver" in argv[0] while initializing Xt.
4964    */
4965   argv[0] = "xscreensaver";
4966   progname = argv[0];
4967
4968
4969   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
4970    */
4971   XtToolkitInitialize ();
4972   app = XtCreateApplicationContext ();
4973   dpy = GDK_DISPLAY();
4974   XtAppSetFallbackResources (app, defaults);
4975   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
4976   toplevel_shell = XtAppCreateShell (progname, progclass,
4977                                      applicationShellWidgetClass,
4978                                      dpy, 0, 0);
4979
4980   dpy = XtDisplay (toplevel_shell);
4981   db = XtDatabase (dpy);
4982   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
4983   XSetErrorHandler (demo_ehandler);
4984
4985   /* Let's just ignore these.  They seem to confuse Irix Gtk... */
4986   signal (SIGPIPE, SIG_IGN);
4987
4988   /* After doing Xt-style command-line processing, complain about any
4989      unrecognized command-line arguments.
4990    */
4991   for (i = 1; i < argc; i++)
4992     {
4993       char *str = argv[i];
4994       if (str[0] == '-' && str[1] == '-')
4995         str++;
4996       if (!strcmp (str, "-prefs"))
4997         prefs_p = True;
4998       else if (!strcmp (str, "-settings"))
4999         settings_p = True;
5000       else if (crapplet_p)
5001         /* There are lots of random args that we don't care about when we're
5002            started as a crapplet, so just ignore unknown args in that case. */
5003         ;
5004       else
5005         {
5006           fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
5007                    argv[i]);
5008           fprintf (stderr, "%s: %s\n", real_progname, usage);
5009           exit (1);
5010         }
5011     }
5012
5013   /* Load the init file, which may end up consulting the X resource database
5014      and the site-wide app-defaults file.  Note that at this point, it's
5015      important that `progname' be "xscreensaver", rather than whatever
5016      was in argv[0].
5017    */
5018   p->db = db;
5019   s->nscreens = screen_count (dpy);
5020
5021   hack_environment (s);  /* must be before initialize_sort_map() */
5022
5023   load_init_file (dpy, p);
5024   initialize_sort_map (s);
5025
5026   /* Now that Xt has been initialized, and the resources have been read,
5027      we can set our `progname' variable to something more in line with
5028      reality.
5029    */
5030   progname = real_progname;
5031
5032
5033 #if 0
5034   /* Print out all the resources we read. */
5035   {
5036     XrmName name = { 0 };
5037     XrmClass class = { 0 };
5038     int count = 0;
5039     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
5040                           (POINTER) &count);
5041   }
5042 #endif
5043
5044
5045   /* Intern the atoms that xscreensaver_command() needs.
5046    */
5047   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
5048   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
5049   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
5050   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
5051   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
5052   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
5053   XA_SELECT = XInternAtom (dpy, "SELECT", False);
5054   XA_DEMO = XInternAtom (dpy, "DEMO", False);
5055   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
5056   XA_BLANK = XInternAtom (dpy, "BLANK", False);
5057   XA_LOCK = XInternAtom (dpy, "LOCK", False);
5058   XA_EXIT = XInternAtom (dpy, "EXIT", False);
5059   XA_RESTART = XInternAtom (dpy, "RESTART", False);
5060
5061
5062   /* Create the window and all its widgets.
5063    */
5064   s->base_widget     = create_xscreensaver_demo ();
5065   s->popup_widget    = create_xscreensaver_settings_dialog ();
5066   s->toplevel_widget = s->base_widget;
5067
5068
5069   /* Set the main window's title. */
5070   {
5071     char *base_title = _("Screensaver Preferences");
5072     char *v = (char *) strdup(strchr(screensaver_id, ' '));
5073     char *s1, *s2, *s3, *s4;
5074     s1 = (char *) strchr(v,  ' '); s1++;
5075     s2 = (char *) strchr(s1, ' ');
5076     s3 = (char *) strchr(v,  '('); s3++;
5077     s4 = (char *) strchr(s3, ')');
5078     *s2 = 0;
5079     *s4 = 0;
5080
5081     window_title = (char *) malloc (strlen (base_title) +
5082                                     strlen (progclass) +
5083                                     strlen (s1) + strlen (s3) +
5084                                     100);
5085     sprintf (window_title, "%s  (%s %s, %s)", base_title, progclass, s1, s3);
5086     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
5087     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
5088     free (v);
5089   }
5090
5091   /* Adjust the (invisible) notebooks on the popup dialog... */
5092   {
5093     GtkNotebook *notebook =
5094       GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
5095     GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
5096     int page = 0;
5097
5098 # ifdef HAVE_XML
5099     gtk_widget_hide (std);
5100 # else  /* !HAVE_XML */
5101     /* Make the advanced page be the only one available. */
5102     gtk_widget_set_sensitive (std, False);
5103     std = GTK_WIDGET (name_to_widget (s, "adv_button"));
5104     gtk_widget_hide (std);
5105     std = GTK_WIDGET (name_to_widget (s, "reset_button"));
5106     gtk_widget_hide (std);
5107     page = 1;
5108 # endif /* !HAVE_XML */
5109
5110     gtk_notebook_set_page (notebook, page);
5111     gtk_notebook_set_show_tabs (notebook, False);
5112   }
5113
5114   /* Various other widget initializations...
5115    */
5116   gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
5117                       GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
5118                       (gpointer) s);
5119   gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
5120                       GTK_SIGNAL_FUNC (wm_popup_close_cb),
5121                       (gpointer) s);
5122
5123   populate_hack_list (s);
5124   populate_prefs_page (s);
5125   sensitize_demo_widgets (s, False);
5126   fix_text_entry_sizes (s);
5127   scroll_to_current_hack (s);
5128
5129   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
5130                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
5131                       (gpointer) s);
5132
5133 #ifndef HAVE_GTK2
5134   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
5135                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
5136                       (gpointer) s);
5137   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
5138                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
5139                       (gpointer) s);
5140 #endif /* !HAVE_GTK2 */
5141
5142   /* Hook up callbacks to the items on the mode menu. */
5143   {
5144     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
5145     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
5146     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
5147     int i;
5148     for (i = 0; kids; kids = kids->next, i++)
5149       {
5150         gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
5151                             GTK_SIGNAL_FUNC (mode_menu_item_cb),
5152                             (gpointer) s);
5153
5154         /* The "random-same" mode menu item does not appear unless
5155            there are multple screens.
5156          */
5157         if (s->nscreens <= 1 &&
5158             mode_menu_order[i] == RANDOM_HACKS_SAME)
5159           gtk_widget_hide (GTK_WIDGET (kids->data));
5160       }
5161
5162     if (s->nscreens <= 1)   /* recompute option-menu size */
5163       {
5164         gtk_widget_unrealize (GTK_WIDGET (menu));
5165         gtk_widget_realize (GTK_WIDGET (menu));
5166       }
5167   }
5168
5169
5170   /* Handle the -prefs command-line argument. */
5171   if (prefs_p)
5172     {
5173       GtkNotebook *notebook =
5174         GTK_NOTEBOOK (name_to_widget (s, "notebook"));
5175       gtk_notebook_set_page (notebook, 1);
5176     }
5177
5178 # ifdef HAVE_CRAPPLET
5179   if (crapplet_p)
5180     {
5181       GtkWidget *capplet;
5182       GtkWidget *outer_vbox;
5183
5184       gtk_widget_hide (s->toplevel_widget);
5185
5186       capplet = capplet_widget_new ();
5187
5188       /* Make there be a "Close" button instead of "OK" and "Cancel" */
5189 # ifdef HAVE_CRAPPLET_IMMEDIATE
5190       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
5191 # endif /* HAVE_CRAPPLET_IMMEDIATE */
5192       /* In crapplet-mode, take off the menubar. */
5193       gtk_widget_hide (name_to_widget (s, "menubar"));
5194
5195       /* Reparent our top-level container to be a child of the capplet
5196          window.
5197        */
5198       outer_vbox = GTK_BIN (s->toplevel_widget)->child;
5199       gtk_widget_ref (outer_vbox);
5200       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
5201                             outer_vbox);
5202       STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
5203       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
5204
5205       /* Find the window above us, and set the title and close handler. */
5206       {
5207         GtkWidget *window = capplet;
5208         while (window && !GTK_IS_WINDOW (window))
5209           window = GET_PARENT (window);
5210         if (window)
5211           {
5212             gtk_window_set_title (GTK_WINDOW (window), window_title);
5213             gtk_signal_connect (GTK_OBJECT (window), "delete_event",
5214                                 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
5215                                 (gpointer) s);
5216           }
5217       }
5218
5219       s->toplevel_widget = capplet;
5220     }
5221 # endif /* HAVE_CRAPPLET */
5222
5223
5224   /* The Gnome folks hate the menubar.  I think it's important to have access
5225      to the commands on the File menu (Restart Daemon, etc.) and to the
5226      About and Documentation commands on the Help menu.
5227    */
5228 #if 0
5229 #ifdef HAVE_GTK2
5230   gtk_widget_hide (name_to_widget (s, "menubar"));
5231 #endif
5232 #endif
5233
5234   free (window_title);
5235   window_title = 0;
5236
5237 #ifdef HAVE_GTK2
5238   /* After picking the default size, allow -geometry to override it. */
5239   if (geom)
5240     gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
5241 #endif
5242
5243   gtk_widget_show (s->toplevel_widget);
5244   init_icon (GET_WINDOW (GTK_WIDGET (s->toplevel_widget)));  /* after `show' */
5245   fix_preview_visual (s);
5246
5247   /* Realize page zero, so that we can diddle the scrollbar when the
5248      user tabs back to it -- otherwise, the current hack isn't scrolled
5249      to the first time they tab back there, when started with "-prefs".
5250      (Though it is if they then tab away, and back again.)
5251
5252      #### Bah!  This doesn't work.  Gtk eats my ass!  Someone who
5253      #### understands this crap, explain to me how to make this work.
5254   */
5255   gtk_widget_realize (name_to_widget (s, "demos_table"));
5256
5257
5258   gtk_timeout_add (60 * 1000, check_blanked_timer, s);
5259
5260
5261   /* Handle the --settings command-line argument. */
5262   if (settings_p)
5263     gtk_timeout_add (500, settings_timer, 0);
5264
5265
5266   /* Issue any warnings about the running xscreensaver daemon. */
5267   if (! s->debug_p)
5268     the_network_is_not_the_computer (s);
5269
5270
5271   /* Run the Gtk event loop, and not the Xt event loop.  This means that
5272      if there were Xt timers or fds registered, they would never get serviced,
5273      and if there were any Xt widgets, they would never have events delivered.
5274      Fortunately, we're using Gtk for all of the UI, and only initialized
5275      Xt so that we could process the command line and use the X resource
5276      manager.
5277    */
5278   s->initializing_p = False;
5279
5280   /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
5281      after we start up.  Otherwise, it always appears scrolled to the top
5282      when in crapplet-mode. */
5283   gtk_timeout_add (500, delayed_scroll_kludge, s);
5284
5285
5286 #if 1
5287   /* Load every configurator in turn, to scan them for errors all at once. */
5288   if (s->debug_p)
5289     {
5290       int i;
5291       for (i = 0; i < p->screenhacks_count; i++)
5292         {
5293           screenhack *hack = p->screenhacks[i];
5294           conf_data *d = load_configurator (hack->command, s->debug_p);
5295           if (d) free_conf_data (d);
5296         }
5297     }
5298 #endif
5299
5300
5301 # ifdef HAVE_CRAPPLET
5302   if (crapplet_p)
5303     capplet_gtk_main ();
5304   else
5305 # endif /* HAVE_CRAPPLET */
5306     gtk_main ();
5307
5308   kill_preview_subproc (s, False);
5309   exit (0);
5310 }
5311
5312 #endif /* HAVE_GTK -- whole file */