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