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