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