02995e08abb0da5910e95ff36eae9a92aad799d3
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2001 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 <stdlib.h>
20
21 #ifdef HAVE_UNISTD_H
22 # include <unistd.h>
23 #endif
24
25 #ifndef VMS
26 # include <pwd.h>               /* for getpwuid() */
27 #else /* VMS */
28 # include "vms-pwd.h"
29 #endif /* VMS */
30
31 #ifdef HAVE_UNAME
32 # include <sys/utsname.h>       /* for uname() */
33 #endif /* HAVE_UNAME */
34
35 #include <stdio.h>
36
37 #include <X11/Xproto.h>         /* for CARD32 */
38 #include <X11/Xatom.h>          /* for XA_INTEGER */
39 #include <X11/Intrinsic.h>
40 #include <X11/StringDefs.h>
41
42 /* We don't actually use any widget internals, but these are included
43    so that gdb will have debug info for the widgets... */
44 #include <X11/IntrinsicP.h>
45 #include <X11/ShellP.h>
46
47 #ifdef HAVE_XMU
48 # ifndef VMS
49 #  include <X11/Xmu/Error.h>
50 # else /* VMS */
51 #  include <Xmu/Error.h>
52 # endif
53 #else
54 # include "xmu.h"
55 #endif
56
57 #include <gtk/gtk.h>
58
59 #ifdef HAVE_CRAPPLET
60 # include <gnome.h>
61 # include <capplet-widget.h>
62 #endif
63
64 extern Display *gdk_display;
65
66 #include "version.h"
67 #include "prefs.h"
68 #include "resources.h"          /* for parse_time() */
69 #include "visual.h"             /* for has_writable_cells() */
70 #include "remote.h"             /* for xscreensaver_command() */
71 #include "usleep.h"
72
73 #include "demo-Gtk-widgets.h"
74
75 #include <stdio.h>
76 #include <string.h>
77 #include <ctype.h>
78
79 #undef countof
80 #define countof(x) (sizeof((x))/sizeof((*x)))
81
82
83 char *progname = 0;
84 char *progclass = "XScreenSaver";
85 XrmDatabase db;
86
87 static Bool crapplet_p = False;
88 static Bool initializing_p;
89 static GtkWidget *toplevel_widget;
90
91 typedef struct {
92   saver_preferences *a, *b;
93 } prefs_pair;
94
95 static void *global_prefs_pair;  /* I hate C so much... */
96
97 char *blurb (void) { return progname; }
98
99 static char *short_version = 0;
100
101 Atom XA_VROOT;
102 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
103 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
104 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
105
106
107 static void populate_demo_window (GtkWidget *toplevel,
108                                   int which, prefs_pair *pair);
109 static void populate_prefs_page (GtkWidget *top, prefs_pair *pair);
110 static int apply_changes_and_save (GtkWidget *widget);
111 static int maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair);
112 static void await_xscreensaver (GtkWidget *widget);
113
114 \f
115 /* Some random utility functions
116  */
117
118 static GtkWidget *
119 name_to_widget (GtkWidget *widget, const char *name)
120 {
121   return (GtkWidget *) gtk_object_get_data (GTK_OBJECT(toplevel_widget), name);
122 }
123
124
125 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
126    Takes a scroller, viewport, or list as an argument.
127  */
128 static void
129 ensure_selected_item_visible (GtkWidget *widget)
130 {
131   GtkScrolledWindow *scroller = 0;
132   GtkViewport *vp = 0;
133   GtkList *list_widget = 0;
134   GList *slist;
135   GList *kids;
136   int nkids = 0;
137   GtkWidget *selected = 0;
138   int which = -1;
139   GtkAdjustment *adj;
140   gint parent_h, child_y, child_h, children_h, ignore;
141   double ratio_t, ratio_b;
142
143   if (GTK_IS_SCROLLED_WINDOW (widget))
144     {
145       scroller = GTK_SCROLLED_WINDOW (widget);
146       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
147       list_widget = GTK_LIST (GTK_BIN(vp)->child);
148     }
149   else if (GTK_IS_VIEWPORT (widget))
150     {
151       vp = GTK_VIEWPORT (widget);
152       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
153       list_widget = GTK_LIST (GTK_BIN(vp)->child);
154     }
155   else if (GTK_IS_LIST (widget))
156     {
157       list_widget = GTK_LIST (widget);
158       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
159       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
160     }
161   else
162     abort();
163
164   slist = list_widget->selection;
165   selected = (slist ? GTK_WIDGET (slist->data) : 0);
166   if (!selected)
167     return;
168
169   which = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
170
171   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
172        kids; kids = kids->next)
173     nkids++;
174
175   adj = gtk_scrolled_window_get_vadjustment (scroller);
176
177   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
178                            &ignore, &ignore, &ignore, &parent_h, &ignore);
179   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
180                            &ignore, &child_y, &ignore, &child_h, &ignore);
181   children_h = nkids * child_h;
182
183   ratio_t = ((double) child_y) / ((double) children_h);
184   ratio_b = ((double) child_y + child_h) / ((double) children_h);
185
186   if (adj->upper == 0.0)  /* no items in list */
187     return;
188
189   if (ratio_t < (adj->value / adj->upper) ||
190       ratio_b > ((adj->value + adj->page_size) / adj->upper))
191     {
192       double target;
193       int slop = parent_h * 0.75; /* how much to overshoot by */
194
195       if (ratio_t < (adj->value / adj->upper))
196         {
197           double ratio_w = ((double) parent_h) / ((double) children_h);
198           double ratio_l = (ratio_b - ratio_t);
199           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
200           target += slop;
201         }
202       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
203         {
204           target = ratio_t * adj->upper;
205           target -= slop;
206         }
207
208       if (target > adj->upper - adj->page_size)
209         target = adj->upper - adj->page_size;
210       if (target < 0)
211         target = 0;
212
213       gtk_adjustment_set_value (adj, target);
214     }
215 }
216
217 static void
218 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
219 {
220   GtkWidget *shell = GTK_WIDGET (user_data);
221   while (shell->parent)
222     shell = shell->parent;
223   gtk_widget_destroy (GTK_WIDGET (shell));
224 }
225
226
227 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
228
229 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
230 {
231   restart_menu_cb (widget, user_data);
232   warning_dialog_dismiss_cb (widget, user_data);
233 }
234
235 static void
236 warning_dialog (GtkWidget *parent, const char *message,
237                 Boolean restart_button_p, int center)
238 {
239   char *msg = strdup (message);
240   char *head;
241
242   GtkWidget *dialog = gtk_dialog_new ();
243   GtkWidget *label = 0;
244   GtkWidget *ok = 0;
245   GtkWidget *cancel = 0;
246   int i = 0;
247
248   while (parent->parent)
249     parent = parent->parent;
250
251   head = msg;
252   while (head)
253     {
254       char name[20];
255       char *s = strchr (head, '\n');
256       if (s) *s = 0;
257
258       sprintf (name, "label%d", i++);
259
260       {
261 #if 0
262         char buf[255];
263 #endif
264         label = gtk_label_new (head);
265 #if 0
266         sprintf (buf, "warning_dialog.%s.font", name);
267         GTK_WIDGET (label)->style = gtk_style_copy (GTK_WIDGET (label)->style);
268         GTK_WIDGET (label)->style->font =
269           gdk_font_load (get_string_resource (buf, "Dialog.Label.Font"));
270 #endif
271         if (center <= 0)
272           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
273         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
274                             label, TRUE, TRUE, 0);
275         gtk_widget_show (label);
276       }
277
278       if (s)
279         head = s+1;
280       else
281         head = 0;
282
283       center--;
284     }
285
286   label = gtk_label_new ("");
287   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
288                       label, TRUE, TRUE, 0);
289   gtk_widget_show (label);
290
291   label = gtk_hbutton_box_new ();
292   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
293                       label, TRUE, TRUE, 0);
294
295   ok = gtk_button_new_with_label ("OK");
296   gtk_container_add (GTK_CONTAINER (label), ok);
297
298   if (restart_button_p)
299     {
300       cancel = gtk_button_new_with_label ("Cancel");
301       gtk_container_add (GTK_CONTAINER (label), cancel);
302     }
303
304   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
305   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
306   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
307   gtk_widget_show (ok);
308   if (cancel)
309     gtk_widget_show (cancel);
310   gtk_widget_show (label);
311   gtk_widget_show (dialog);
312 /*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
313
314   if (restart_button_p)
315     {
316       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
317                                  GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
318                                  (gpointer) dialog);
319       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
320                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
321                                  (gpointer) dialog);
322     }
323   else
324     {
325       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
326                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
327                                  (gpointer) dialog);
328     }
329   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
330                                 GTK_WIDGET (parent)->window);
331
332   gdk_window_show (GTK_WIDGET (dialog)->window);
333   gdk_window_raise (GTK_WIDGET (dialog)->window);
334
335   free (msg);
336 }
337
338
339 static void
340 run_cmd (GtkWidget *widget, Atom command, int arg)
341 {
342   char *err = 0;
343   int status;
344
345   apply_changes_and_save (widget);
346   status = xscreensaver_command (gdk_display, command, arg, False, &err);
347   if (status < 0)
348     {
349       char buf [255];
350       if (err)
351         sprintf (buf, "Error:\n\n%s", err);
352       else
353         strcpy (buf, "Unknown error!");
354       warning_dialog (widget, buf, False, 100);
355     }
356   if (err) free (err);
357 }
358
359
360 static void
361 run_hack (GtkWidget *widget, int which, Bool report_errors_p)
362 {
363   if (which < 0) return;
364   apply_changes_and_save (widget);
365   if (report_errors_p)
366     run_cmd (widget, XA_DEMO, which + 1);
367   else
368     {
369       char *s = 0;
370       xscreensaver_command (gdk_display, XA_DEMO, which + 1, False, &s);
371       if (s) free (s);
372     }
373 }
374
375
376 \f
377 /* Button callbacks
378  */
379
380 void
381 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
382 {
383   apply_changes_and_save (GTK_WIDGET (menuitem));
384   gtk_main_quit ();
385 }
386
387 static void
388 wm_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
389 {
390   apply_changes_and_save (widget);
391   gtk_main_quit ();
392 }
393
394
395 void
396 cut_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
397 {
398   /* #### */
399   warning_dialog (GTK_WIDGET (menuitem),
400                   "Error:\n\n"
401                   "cut unimplemented\n", False, 1);
402 }
403
404
405 void
406 copy_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
407 {
408   /* #### */
409   warning_dialog (GTK_WIDGET (menuitem),
410                   "Error:\n\n"
411                   "copy unimplemented\n", False, 1);
412 }
413
414
415 void
416 paste_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
417 {
418   /* #### */
419   warning_dialog (GTK_WIDGET (menuitem),
420                   "Error:\n\n"
421                   "paste unimplemented\n", False, 1);
422 }
423
424
425 void
426 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
427 {
428   char buf [2048];
429   char *s = strdup (screensaver_id + 4);
430   char *s2;
431
432   s2 = strchr (s, ',');
433   *s2 = 0;
434   s2 += 2;
435
436   sprintf (buf, "%s\n%s\n\n"
437            "For updates, check http://www.jwz.org/xscreensaver/",
438            s, s2);
439   free (s);
440
441   warning_dialog (GTK_WIDGET (menuitem), buf, False, 100);
442 }
443
444
445 void
446 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
447 {
448   /* prefs_pair *pair = (prefs_pair *) client_data; */
449   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
450
451   saver_preferences *p =  pair->a;
452   char *help_command;
453
454   if (!p->help_url || !*p->help_url)
455     {
456       warning_dialog (GTK_WIDGET (menuitem),
457                       "Error:\n\n"
458                       "No Help URL has been specified.\n", False, 100);
459       return;
460     }
461
462   help_command = (char *) malloc (strlen (p->load_url_command) +
463                                   (strlen (p->help_url) * 2) + 20);
464   strcpy (help_command, "( ");
465   sprintf (help_command + strlen(help_command),
466            p->load_url_command, p->help_url, p->help_url);
467   strcat (help_command, " ) &");
468   system (help_command);
469   free (help_command);
470 }
471
472
473 void
474 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
475 {
476   run_cmd (GTK_WIDGET (menuitem), XA_ACTIVATE, 0);
477 }
478
479
480 void
481 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
482 {
483   run_cmd (GTK_WIDGET (menuitem), XA_LOCK, 0);
484 }
485
486
487 void
488 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
489 {
490   run_cmd (GTK_WIDGET (menuitem), XA_EXIT, 0);
491 }
492
493
494 void
495 restart_menu_cb (GtkWidget *widget, gpointer user_data)
496 {
497 #if 0
498   run_cmd (GTK_WIDGET (widget), XA_RESTART, 0);
499 #else
500   apply_changes_and_save (GTK_WIDGET (widget));
501   xscreensaver_command (gdk_display, XA_EXIT, 0, False, NULL);
502   sleep (1);
503   system ("xscreensaver -nosplash &");
504 #endif
505
506   await_xscreensaver (GTK_WIDGET (widget));
507 }
508
509 static void
510 await_xscreensaver (GtkWidget *widget)
511 {
512   int countdown = 5;
513
514   Display *dpy = gdk_display;
515   /*  GtkWidget *dialog = 0;*/
516   char *rversion = 0;
517
518   while (!rversion && (--countdown > 0))
519     {
520       /* Check for the version of the running xscreensaver... */
521       server_xscreensaver_version (dpy, &rversion, 0, 0);
522
523       /* If it's not there yet, wait a second... */
524       sleep (1);
525     }
526
527 /*  if (dialog) gtk_widget_destroy (dialog);*/
528
529   if (rversion)
530     {
531       /* Got it. */
532       free (rversion);
533     }
534   else
535     {
536       /* Timed out, no screensaver running. */
537
538       char buf [1024];
539       Bool root_p = (geteuid () == 0);
540       
541       strcpy (buf, 
542               "Error:\n\n"
543               "The xscreensaver daemon did not start up properly.\n"
544               "\n");
545
546       if (root_p)
547         strcat (buf,
548             "You are running as root.  This usually means that xscreensaver\n"
549             "was unable to contact your X server because access control is\n"
550             "turned on.  Try running this command:\n"
551             "\n"
552             "                        xhost +localhost\n"
553             "\n"
554             "and then selecting `File / Restart Daemon'.\n"
555             "\n"
556             "Note that turning off access control will allow anyone logged\n"
557             "on to this machine to access your screen, which might be\n"
558             "considered a security problem.  Please read the xscreensaver\n"
559             "manual and FAQ for more information.\n"
560             "\n"
561             "You shouldn't run X as root. Instead, you should log in as a\n"
562             "normal user, and `su' as necessary.");
563       else
564         strcat (buf, "Please check your $PATH and permissions.");
565
566       warning_dialog (widget, buf, False, 1);
567     }
568 }
569
570
571 static int _selected_hack_number = -1;
572
573 static int
574 selected_hack_number (GtkWidget *toplevel)
575 {
576 #if 0
577   GtkViewport *vp = GTK_VIEWPORT (name_to_widget (toplevel, "viewport"));
578   GtkList *list_widget = GTK_LIST (GTK_BIN(vp)->child);
579   GList *slist = list_widget->selection;
580   GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0);
581   int which = (selected
582                ? gtk_list_child_position (list_widget, GTK_WIDGET (selected))
583                : -1);
584   return which;
585 #else
586   return _selected_hack_number;
587 #endif
588 }
589
590
591 static int
592 demo_write_init_file (GtkWidget *widget, saver_preferences *p)
593 {
594   if (!write_init_file (p, short_version, False))
595     return 0;
596   else
597     {
598       const char *f = init_file_name();
599       if (!f || !*f)
600         warning_dialog (widget,
601                         "Error:\n\nCouldn't determine init file name!\n",
602                         False, 100);
603       else
604         {
605           char *b = (char *) malloc (strlen(f) + 1024);
606           sprintf (b, "Error:\n\nCouldn't write %s\n", f);
607           warning_dialog (widget, b, False, 100);
608           free (b);
609         }
610       return -1;
611     }
612 }
613
614
615 static int
616 apply_changes_and_save_1 (GtkWidget *widget)
617 {
618   /* prefs_pair *pair = (prefs_pair *) client_data; */
619   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
620   saver_preferences *p =  pair->a;
621   GtkList *list_widget =
622     GTK_LIST (name_to_widget (widget, "list"));
623   int which = selected_hack_number (widget);
624
625   GtkEntry *cmd = GTK_ENTRY (name_to_widget (widget, "cmd_text"));
626   GtkToggleButton *enabled =
627     GTK_TOGGLE_BUTTON (name_to_widget (widget, "enabled"));
628   GtkCombo *vis = GTK_COMBO (name_to_widget (widget, "visual_combo"));
629
630   Bool enabled_p = gtk_toggle_button_get_active (enabled);
631   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
632   const char *command = gtk_entry_get_text (cmd);
633   
634   char c;
635   unsigned long id;
636
637   if (which < 0) return -1;
638
639   if (maybe_reload_init_file (widget, pair) != 0)
640     return 1;
641
642   /* Sanity-check and canonicalize whatever the user typed into the combo box.
643    */
644   if      (!strcasecmp (visual, ""))                   visual = "";
645   else if (!strcasecmp (visual, "any"))                visual = "";
646   else if (!strcasecmp (visual, "default"))            visual = "Default";
647   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
648   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
649   else if (!strcasecmp (visual, "best"))               visual = "Best";
650   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
651   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
652   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
653   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
654   else if (!strcasecmp (visual, "color"))              visual = "Color";
655   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
656   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
657   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
658   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
659   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
660   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
661   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
662   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
663   else if (1 == sscanf (visual, " %ld %c", &id, &c))   ;
664   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
665   else
666     {
667       gdk_beep ();                                /* unparsable */
668       visual = "";
669       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");
670     }
671
672   ensure_selected_item_visible (GTK_WIDGET (list_widget));
673
674   if (!p->screenhacks[which]->visual)
675     p->screenhacks[which]->visual = strdup ("");
676   if (!p->screenhacks[which]->command)
677     p->screenhacks[which]->command = strdup ("");
678
679   if (p->screenhacks[which]->enabled_p != enabled_p ||
680       !!strcasecmp (p->screenhacks[which]->visual, visual) ||
681       !!strcasecmp (p->screenhacks[which]->command, command))
682     {
683       /* Something was changed -- store results into the struct,
684          and write the file.
685        */
686       free (p->screenhacks[which]->visual);
687       free (p->screenhacks[which]->command);
688       p->screenhacks[which]->visual = strdup (visual);
689       p->screenhacks[which]->command = strdup (command);
690       p->screenhacks[which]->enabled_p = enabled_p;
691
692       return demo_write_init_file (widget, p);
693     }
694
695   /* No changes made */
696   return 0;
697 }
698
699 void prefs_ok_cb (GtkButton *button, gpointer user_data);
700
701 static int
702 apply_changes_and_save (GtkWidget *widget)
703 {
704   prefs_ok_cb ((GtkButton *) widget, 0);
705   return apply_changes_and_save_1 (widget);
706 }
707
708
709 void
710 run_this_cb (GtkButton *button, gpointer user_data)
711 {
712   int which = selected_hack_number (GTK_WIDGET (button));
713   if (which < 0) return;
714   if (0 == apply_changes_and_save (GTK_WIDGET (button)))
715     run_hack (GTK_WIDGET (button), which, True);
716 }
717
718
719 void
720 manual_cb (GtkButton *button, gpointer user_data)
721 {
722   /* prefs_pair *pair = (prefs_pair *) client_data; */
723   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
724   saver_preferences *p =  pair->a;
725   GtkList *list_widget =
726     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
727   int which = selected_hack_number (GTK_WIDGET (button));
728   char *name, *name2, *cmd, *s;
729   if (which < 0) return;
730   apply_changes_and_save (GTK_WIDGET (button));
731   ensure_selected_item_visible (GTK_WIDGET (list_widget));
732
733   name = strdup (p->screenhacks[which]->command);
734   name2 = name;
735   while (isspace (*name2)) name2++;
736   s = name2;
737   while (*s && !isspace (*s)) s++;
738   *s = 0;
739   s = strrchr (name2, '/');
740   if (s) name = s+1;
741
742   cmd = get_string_resource ("manualCommand", "ManualCommand");
743   if (cmd)
744     {
745       char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100);
746       strcpy (cmd2, "( ");
747       sprintf (cmd2 + strlen (cmd2),
748                cmd,
749                name2, name2, name2, name2);
750       strcat (cmd2, " ) &");
751       system (cmd2);
752       free (cmd2);
753     }
754   else
755     {
756       warning_dialog (GTK_WIDGET (button),
757                       "Error:\n\nno `manualCommand' resource set.",
758                       False, 100);
759     }
760
761   free (name);
762 }
763
764
765 void
766 run_next_cb (GtkButton *button, gpointer user_data)
767 {
768   /* prefs_pair *pair = (prefs_pair *) client_data; */
769   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
770   saver_preferences *p =  pair->a;
771
772   GtkList *list_widget =
773     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
774   int which = selected_hack_number (GTK_WIDGET (button));
775
776   if (which < 0)
777     which = 0;
778   else
779     which++;
780
781   if (which >= p->screenhacks_count)
782     which = 0;
783
784   apply_changes_and_save (GTK_WIDGET (button));
785   gtk_list_select_item (GTK_LIST (list_widget), which);
786   ensure_selected_item_visible (GTK_WIDGET (list_widget));
787   populate_demo_window (GTK_WIDGET (button), which, pair);
788   run_hack (GTK_WIDGET (button), which, False);
789 }
790
791
792 void
793 run_prev_cb (GtkButton *button, gpointer user_data)
794 {
795   /* prefs_pair *pair = (prefs_pair *) client_data; */
796   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
797   saver_preferences *p =  pair->a;
798
799   GtkList *list_widget =
800     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
801   int which = selected_hack_number (GTK_WIDGET (button));
802
803   if (which < 0)
804     which = p->screenhacks_count - 1;
805   else
806     which--;
807
808   if (which < 0)
809     which = p->screenhacks_count - 1;
810
811   apply_changes_and_save (GTK_WIDGET (button));
812   gtk_list_select_item (GTK_LIST (list_widget), which);
813   ensure_selected_item_visible (GTK_WIDGET (list_widget));
814   populate_demo_window (GTK_WIDGET (button), which, pair);
815   run_hack (GTK_WIDGET (button), which, False);
816 }
817
818
819 /* Helper for the text fields that contain time specifications:
820    this parses the text, and does error checking.
821  */
822 static void 
823 hack_time_text (GtkWidget *widget, const char *line, Time *store, Bool sec_p)
824 {
825   if (*line)
826     {
827       int value;
828       value = parse_time ((char *) line, sec_p, True);
829       value *= 1000;    /* Time measures in microseconds */
830       if (value < 0)
831         {
832           char b[255];
833           sprintf (b,
834                    "Error:\n\n"
835                    "Unparsable time format: \"%s\"\n",
836                    line);
837           warning_dialog (widget, b, False, 100);
838         }
839       else
840         *store = value;
841     }
842 }
843
844
845 void
846 prefs_ok_cb (GtkButton *button, gpointer user_data)
847 {
848   /* prefs_pair *pair = (prefs_pair *) client_data; */
849   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
850
851   saver_preferences *p =  pair->a;
852   saver_preferences *p2 = pair->b;
853   Bool changed = False;
854
855 # define SECONDS(field, name) \
856   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
857                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
858                   (field), \
859                   True)
860
861 # define MINUTES(field, name) \
862   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
863                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
864                   (field), \
865                   False)
866
867 # define INTEGER(field, name) do { \
868     char *line = gtk_entry_get_text (\
869                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))); \
870     unsigned int value; \
871     char c; \
872     if (! *line) \
873       ; \
874     else if (sscanf (line, "%u%c", &value, &c) != 1) \
875       { \
876         char b[255]; \
877         sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", line); \
878         warning_dialog (GTK_WIDGET (button), b, False, 100); \
879       } \
880    else \
881      *(field) = value; \
882   } while(0)
883
884 # define CHECKBOX(field, name) \
885   field = gtk_toggle_button_get_active (\
886              GTK_TOGGLE_BUTTON (name_to_widget (GTK_WIDGET(button), (name))))
887
888   MINUTES (&p2->timeout,          "timeout_text");
889   MINUTES (&p2->cycle,            "cycle_text");
890   CHECKBOX (p2->lock_p,           "lock_button");
891   MINUTES (&p2->lock_timeout,     "lock_text");
892
893   CHECKBOX (p2->dpms_enabled_p,   "dpms_button");
894   MINUTES (&p2->dpms_standby,     "dpms_standby_text");
895   MINUTES (&p2->dpms_suspend,     "dpms_suspend_text");
896   MINUTES (&p2->dpms_off,         "dpms_off_text");
897
898   CHECKBOX (p2->verbose_p,        "verbose_button");
899   CHECKBOX (p2->capture_stderr_p, "capture_button");
900   CHECKBOX (p2->splash_p,         "splash_button");
901
902   CHECKBOX (p2->install_cmap_p,   "install_button");
903   CHECKBOX (p2->fade_p,           "fade_button");
904   CHECKBOX (p2->unfade_p,         "unfade_button");
905   SECONDS (&p2->fade_seconds,     "fade_text");
906
907 # undef SECONDS
908 # undef MINUTES
909 # undef INTEGER
910 # undef CHECKBOX
911
912 # define COPY(field) \
913   if (p->field != p2->field) changed = True; \
914   p->field = p2->field
915
916   COPY(timeout);
917   COPY(cycle);
918   COPY(lock_p);
919   COPY(lock_timeout);
920
921   COPY(dpms_enabled_p);
922   COPY(dpms_standby);
923   COPY(dpms_suspend);
924   COPY(dpms_off);
925
926   COPY(verbose_p);
927   COPY(capture_stderr_p);
928   COPY(splash_p);
929
930   COPY(install_cmap_p);
931   COPY(fade_p);
932   COPY(unfade_p);
933   COPY(fade_seconds);
934 # undef COPY
935
936   populate_prefs_page (GTK_WIDGET (button), pair);
937
938   if (changed)
939     {
940       Display *dpy = gdk_display;
941       sync_server_dpms_settings (dpy, p->dpms_enabled_p,
942                                  p->dpms_standby / 1000,
943                                  p->dpms_suspend / 1000,
944                                  p->dpms_off / 1000,
945                                  False);
946
947       demo_write_init_file (GTK_WIDGET (button), p);
948     }
949 }
950
951
952 void
953 prefs_cancel_cb (GtkButton *button, gpointer user_data)
954 {
955   /* prefs_pair *pair = (prefs_pair *) client_data; */
956   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
957
958   *pair->b = *pair->a;
959   populate_prefs_page (GTK_WIDGET (button), pair);
960 }
961
962
963 void
964 pref_changed_cb (GtkButton *button, gpointer user_data)
965 {
966   if (! initializing_p)
967     apply_changes_and_save (GTK_WIDGET (button));
968 }
969
970
971 static gint
972 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
973                      gpointer client_data)
974 {
975   if (event->type == GDK_2BUTTON_PRESS)
976     {
977       GtkList *list = GTK_LIST (name_to_widget (button, "list"));
978       int which = gtk_list_child_position (list, GTK_WIDGET (button));
979
980       if (which >= 0)
981         run_hack (GTK_WIDGET (button), which, True);
982     }
983
984   return FALSE;
985 }
986
987
988 static void
989 list_select_cb (GtkList *list, GtkWidget *child)
990 {
991   /* prefs_pair *pair = (prefs_pair *) client_data; */
992   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
993
994   int which = gtk_list_child_position (list, GTK_WIDGET (child));
995   apply_changes_and_save (GTK_WIDGET (list));
996   populate_demo_window (GTK_WIDGET (list), which, pair);
997 }
998
999 static void
1000 list_unselect_cb (GtkList *list, GtkWidget *child)
1001 {
1002   /* prefs_pair *pair = (prefs_pair *) client_data; */
1003   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
1004
1005   apply_changes_and_save (GTK_WIDGET (list));
1006   populate_demo_window (GTK_WIDGET (list), -1, pair);
1007 }
1008
1009
1010 static int updating_enabled_cb = 0;  /* kludge to make sure that enabled_cb
1011                                         is only run by user action, not by
1012                                         program action. */
1013
1014 /* Called when the checkboxes that are in the left column of the
1015    scrolling list are clicked.  This both populates the right pane
1016    (just as clicking on the label (really, listitem) does) and
1017    also syncs this checkbox with  the right pane Enabled checkbox.
1018  */
1019 static void
1020 list_checkbox_cb (GtkWidget *cb, gpointer client_data)
1021 {
1022   prefs_pair *pair = (prefs_pair *) client_data;
1023
1024   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
1025   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
1026
1027   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
1028   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
1029   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
1030   GtkAdjustment *adj;
1031   double scroll_top;
1032
1033   GtkToggleButton *enabled =
1034     GTK_TOGGLE_BUTTON (name_to_widget (cb, "enabled"));
1035
1036   int which = gtk_list_child_position (list, line);
1037
1038   /* remember previous scroll position of the top of the list */
1039   adj = gtk_scrolled_window_get_vadjustment (scroller);
1040   scroll_top = adj->value;
1041
1042   apply_changes_and_save (GTK_WIDGET (list));
1043   gtk_list_select_item (list, which);
1044   /* ensure_selected_item_visible (GTK_WIDGET (list)); */
1045   populate_demo_window (GTK_WIDGET (list), which, pair);
1046   
1047   updating_enabled_cb++;
1048   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (enabled),
1049                                 GTK_TOGGLE_BUTTON (cb)->active);
1050   updating_enabled_cb--;
1051
1052   /* restore the previous scroll position of the top of the list.
1053      this is weak, but I don't really know why it's moving... */
1054   gtk_adjustment_set_value (adj, scroll_top);
1055 }
1056
1057
1058 /* Called when the right pane Enabled checkbox is clicked.  This syncs
1059    the corresponding checkbox inside the scrolling list to the state
1060    of this checkbox.
1061  */
1062 void
1063 enabled_cb (GtkWidget *cb, gpointer client_data)
1064 {
1065   int which = selected_hack_number (cb);
1066   
1067   if (updating_enabled_cb) return;
1068
1069   if (which != -1)
1070     {
1071       GtkList *list = GTK_LIST (name_to_widget (cb, "list"));
1072       GList *kids = GTK_LIST (list)->children;
1073       GtkWidget *line = GTK_WIDGET (g_list_nth_data (kids, which));
1074       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1075       GtkWidget *line_check =
1076         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1077
1078       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
1079                                     GTK_TOGGLE_BUTTON (cb)->active);
1080     }
1081 }
1082
1083 \f
1084 /* Populating the various widgets
1085  */
1086
1087
1088 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
1089  */
1090 static void
1091 format_time (char *buf, Time time)
1092 {
1093   int s = time / 1000;
1094   unsigned int h = 0, m = 0;
1095   if (s >= 60)
1096     {
1097       m += (s / 60);
1098       s %= 60;
1099     }
1100   if (m >= 60)
1101     {
1102       h += (m / 60);
1103       m %= 60;
1104     }
1105   sprintf (buf, "%u:%02u:%02u", h, m, s);
1106 }
1107
1108
1109 /* Finds the number of the last hack to run, and makes that item be
1110    selected by default.
1111  */
1112 static void
1113 scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair)
1114 {
1115   saver_preferences *p =  pair->a;
1116   Atom type;
1117   int format;
1118   unsigned long nitems, bytesafter;
1119   CARD32 *data = 0;
1120   Display *dpy = gdk_display;
1121   int which = 0;
1122   GtkList *list;
1123
1124   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
1125                           XA_SCREENSAVER_STATUS,
1126                           0, 3, False, XA_INTEGER,
1127                           &type, &format, &nitems, &bytesafter,
1128                           (unsigned char **) &data)
1129       == Success
1130       && type == XA_INTEGER
1131       && nitems >= 3
1132       && data)
1133     which = (int) data[2] - 1;
1134
1135   if (data) free (data);
1136
1137   if (which < 0)
1138     return;
1139
1140   list = GTK_LIST (name_to_widget (toplevel, "list"));
1141   apply_changes_and_save (toplevel);
1142   if (which < p->screenhacks_count)
1143     {
1144       gtk_list_select_item (list, which);
1145       ensure_selected_item_visible (GTK_WIDGET (list));
1146       populate_demo_window (toplevel, which, pair);
1147     }
1148 }
1149
1150
1151
1152 static void
1153 populate_hack_list (GtkWidget *toplevel, prefs_pair *pair)
1154 {
1155   saver_preferences *p =  pair->a;
1156   GtkList *list = GTK_LIST (name_to_widget (toplevel, "list"));
1157   screenhack **hacks = p->screenhacks;
1158   screenhack **h;
1159
1160   for (h = hacks; h && *h; h++)
1161     {
1162       /* A GtkList must contain only GtkListItems, but those can contain
1163          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
1164          and a Label.  We handle single and double click events on the
1165          line itself, for clicking on the text, but the interior checkbox
1166          also handles its own events.
1167        */
1168       GtkWidget *line;
1169       GtkWidget *line_hbox;
1170       GtkWidget *line_check;
1171       GtkWidget *line_label;
1172
1173       char *pretty_name = (h[0]->name
1174                            ? strdup (h[0]->name)
1175                            : make_hack_name (h[0]->command));
1176
1177       line = gtk_list_item_new ();
1178       line_hbox = gtk_hbox_new (FALSE, 0);
1179       line_check = gtk_check_button_new ();
1180       line_label = gtk_label_new (pretty_name);
1181
1182       gtk_container_add (GTK_CONTAINER (line), line_hbox);
1183       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
1184       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
1185
1186       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
1187                                     h[0]->enabled_p);
1188       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
1189
1190       gtk_widget_show (line_check);
1191       gtk_widget_show (line_label);
1192       gtk_widget_show (line_hbox);
1193       gtk_widget_show (line);
1194
1195       free (pretty_name);
1196
1197       gtk_container_add (GTK_CONTAINER (list), line);
1198       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
1199                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
1200                           (gpointer) pair);
1201
1202       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
1203                           GTK_SIGNAL_FUNC (list_checkbox_cb),
1204                           (gpointer) pair);
1205
1206 #if 0 /* #### */
1207       GTK_WIDGET (GTK_BIN(line)->child)->style =
1208         gtk_style_copy (GTK_WIDGET (text_line)->style);
1209 #endif
1210       gtk_widget_show (line);
1211     }
1212
1213   gtk_signal_connect (GTK_OBJECT (list), "select_child",
1214                       GTK_SIGNAL_FUNC (list_select_cb),
1215                       (gpointer) pair);
1216   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
1217                       GTK_SIGNAL_FUNC (list_unselect_cb),
1218                       (gpointer) pair);
1219 }
1220
1221
1222 static void
1223 populate_prefs_page (GtkWidget *top, prefs_pair *pair)
1224 {
1225   saver_preferences *p =  pair->a;
1226   char s[100];
1227
1228   format_time (s, p->timeout);
1229   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "timeout_text")), s);
1230   format_time (s, p->cycle);
1231   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "cycle_text")), s);
1232   format_time (s, p->lock_timeout);
1233   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "lock_text")), s);
1234
1235   format_time (s, p->dpms_standby);
1236   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_standby_text")),s);
1237   format_time (s, p->dpms_suspend);
1238   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_suspend_text")),s);
1239   format_time (s, p->dpms_off);
1240   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_off_text")), s);
1241
1242   format_time (s, p->fade_seconds);
1243   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "fade_text")), s);
1244
1245   gtk_toggle_button_set_active (
1246                    GTK_TOGGLE_BUTTON (name_to_widget (top, "lock_button")),
1247                    p->lock_p);
1248   gtk_toggle_button_set_active (
1249                    GTK_TOGGLE_BUTTON (name_to_widget (top, "verbose_button")),
1250                    p->verbose_p);
1251   gtk_toggle_button_set_active (
1252                    GTK_TOGGLE_BUTTON (name_to_widget (top, "capture_button")),
1253                    p->capture_stderr_p);
1254
1255   gtk_toggle_button_set_active (
1256                    GTK_TOGGLE_BUTTON (name_to_widget (top, "dpms_button")),
1257                    p->dpms_enabled_p);
1258
1259   gtk_toggle_button_set_active (
1260                    GTK_TOGGLE_BUTTON (name_to_widget (top, "install_button")),
1261                    p->install_cmap_p);
1262   gtk_toggle_button_set_active (
1263                    GTK_TOGGLE_BUTTON (name_to_widget (top, "fade_button")),
1264                    p->fade_p);
1265   gtk_toggle_button_set_active (
1266                    GTK_TOGGLE_BUTTON (name_to_widget (top, "unfade_button")),
1267                    p->unfade_p);
1268
1269
1270   {
1271     Bool found_any_writable_cells = False;
1272     Bool dpms_supported = False;
1273
1274     Display *dpy = gdk_display;
1275     int nscreens = ScreenCount(dpy);
1276     int i;
1277     for (i = 0; i < nscreens; i++)
1278       {
1279         Screen *s = ScreenOfDisplay (dpy, i);
1280         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1281           {
1282             found_any_writable_cells = True;
1283             break;
1284           }
1285       }
1286
1287 #ifdef HAVE_DPMS_EXTENSION
1288     {
1289       int op = 0, event = 0, error = 0;
1290       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
1291         dpms_supported = True;
1292     }
1293 #endif /* HAVE_DPMS_EXTENSION */
1294
1295
1296     /* Blanking and Locking
1297      */
1298     gtk_widget_set_sensitive (
1299                            GTK_WIDGET (name_to_widget (top, "lock_label")),
1300                            p->lock_p);
1301     gtk_widget_set_sensitive (
1302                            GTK_WIDGET (name_to_widget (top, "lock_text")),
1303                            p->lock_p);
1304
1305     /* DPMS
1306      */
1307     gtk_widget_set_sensitive (
1308                       GTK_WIDGET (name_to_widget (top, "dpms_frame")),
1309                       dpms_supported);
1310     gtk_widget_set_sensitive (
1311                       GTK_WIDGET (name_to_widget (top, "dpms_button")),
1312                       dpms_supported);
1313     gtk_widget_set_sensitive (
1314                        GTK_WIDGET (name_to_widget (top, "dpms_standby_label")),
1315                        dpms_supported && p->dpms_enabled_p);
1316     gtk_widget_set_sensitive (
1317                        GTK_WIDGET (name_to_widget (top, "dpms_standby_text")),
1318                        dpms_supported && p->dpms_enabled_p);
1319     gtk_widget_set_sensitive (
1320                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_label")),
1321                        dpms_supported && p->dpms_enabled_p);
1322     gtk_widget_set_sensitive (
1323                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_text")),
1324                        dpms_supported && p->dpms_enabled_p);
1325     gtk_widget_set_sensitive (
1326                        GTK_WIDGET (name_to_widget (top, "dpms_off_label")),
1327                        dpms_supported && p->dpms_enabled_p);
1328     gtk_widget_set_sensitive (
1329                        GTK_WIDGET (name_to_widget (top, "dpms_off_text")),
1330                        dpms_supported && p->dpms_enabled_p);
1331
1332     /* Colormaps
1333      */
1334     gtk_widget_set_sensitive (
1335                            GTK_WIDGET (name_to_widget (top, "cmap_frame")),
1336                            found_any_writable_cells);
1337     gtk_widget_set_sensitive (
1338                            GTK_WIDGET (name_to_widget (top, "install_button")),
1339                            found_any_writable_cells);
1340     gtk_widget_set_sensitive (
1341                            GTK_WIDGET (name_to_widget (top, "fade_button")),
1342                            found_any_writable_cells);
1343     gtk_widget_set_sensitive (
1344                            GTK_WIDGET (name_to_widget (top, "unfade_button")),
1345                            found_any_writable_cells);
1346
1347     gtk_widget_set_sensitive (
1348                            GTK_WIDGET (name_to_widget (top, "fade_label")),
1349                            (found_any_writable_cells &&
1350                             (p->fade_p || p->unfade_p)));
1351     gtk_widget_set_sensitive (
1352                            GTK_WIDGET (name_to_widget (top, "fade_text")),
1353                            (found_any_writable_cells &&
1354                             (p->fade_p || p->unfade_p)));
1355   }
1356
1357 }
1358
1359
1360 static void
1361 sensitize_demo_widgets (GtkWidget *toplevel, Bool sensitive_p)
1362 {
1363   const char *names[] = { "cmd_label", "cmd_text", "enabled",
1364                           "visual", "visual_combo",
1365                           "demo", "manual" };
1366   int i;
1367   for (i = 0; i < countof(names); i++)
1368     {
1369       GtkWidget *w = name_to_widget (toplevel, names[i]);
1370       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
1371     }
1372
1373   /* I don't know how to handle these yet... */
1374   {
1375     const char *names2[] = { "cut_menu", "copy_menu", "paste_menu" };
1376     for (i = 0; i < countof(names2); i++)
1377       {
1378         GtkWidget *w = name_to_widget (toplevel, names2[i]);
1379         gtk_widget_set_sensitive (GTK_WIDGET(w), False);
1380       }
1381   }
1382 }
1383
1384
1385 /* Even though we've given these text fields a maximum number of characters,
1386    their default size is still about 30 characters wide -- so measure out
1387    a string in their font, and resize them to just fit that.
1388  */
1389 static void
1390 fix_text_entry_sizes (GtkWidget *toplevel)
1391 {
1392   const char *names[] = { "timeout_text", "cycle_text", "lock_text",
1393                           "dpms_standby_text", "dpms_suspend_text",
1394                           "dpms_off_text", "fade_text" };
1395   int i;
1396   int width = 0;
1397   GtkWidget *w;
1398
1399   for (i = 0; i < countof(names); i++)
1400     {
1401       w = GTK_WIDGET (name_to_widget (toplevel, names[i]));
1402       if (width == 0)
1403         width = gdk_text_width (w->style->font, "00:00:00_", 9);
1404       gtk_widget_set_usize (w, width, -2);
1405     }
1406
1407   /* Now fix the size of the combo box.
1408    */
1409   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "visual_combo"));
1410   w = GTK_COMBO (w)->entry;
1411   width = gdk_text_width (w->style->font, "PseudoColor___", 14);
1412   gtk_widget_set_usize (w, width, -2);
1413
1414 #if 0
1415   /* Now fix the size of the list.
1416    */
1417   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "list"));
1418   width = gdk_text_width (w->style->font, "nnnnnnnnnnnnnnnnnnnnnn", 22);
1419   gtk_widget_set_usize (w, width, -2);
1420 #endif
1421 }
1422
1423
1424
1425 \f
1426 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
1427  */
1428
1429 static char *up_arrow_xpm[] = {
1430   "15 15 4 1",
1431   "     c None",
1432   "-    c #FFFFFF",
1433   "+    c #D6D6D6",
1434   "@    c #000000",
1435
1436   "       @       ",
1437   "       @       ",
1438   "      -+@      ",
1439   "      -+@      ",
1440   "     -+++@     ",
1441   "     -+++@     ",
1442   "    -+++++@    ",
1443   "    -+++++@    ",
1444   "   -+++++++@   ",
1445   "   -+++++++@   ",
1446   "  -+++++++++@  ",
1447   "  -+++++++++@  ",
1448   " -+++++++++++@ ",
1449   " @@@@@@@@@@@@@ ",
1450   "               ",
1451
1452   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1453      the end of the array (Gtk 1.2.5.) */
1454   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1455   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1456 };
1457
1458 static char *down_arrow_xpm[] = {
1459   "15 15 4 1",
1460   "     c None",
1461   "-    c #FFFFFF",
1462   "+    c #D6D6D6",
1463   "@    c #000000",
1464
1465   "               ",
1466   " ------------- ",
1467   " -+++++++++++@ ",
1468   "  -+++++++++@  ",
1469   "  -+++++++++@  ",
1470   "   -+++++++@   ",
1471   "   -+++++++@   ",
1472   "    -+++++@    ",
1473   "    -+++++@    ",
1474   "     -+++@     ",
1475   "     -+++@     ",
1476   "      -+@      ",
1477   "      -+@      ",
1478   "       @       ",
1479   "       @       ",
1480
1481   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1482      the end of the array (Gtk 1.2.5.) */
1483   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1484   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1485 };
1486
1487 static void
1488 pixmapify_button (GtkWidget *toplevel, int down_p)
1489 {
1490   GdkPixmap *pixmap;
1491   GdkBitmap *mask;
1492   GtkWidget *pixmapwid;
1493   GtkStyle *style;
1494   GtkWidget *w;
1495
1496   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel),
1497                                   (down_p ? "next" : "prev")));
1498   style = gtk_widget_get_style (w);
1499   mask = 0;
1500   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
1501                                          &style->bg[GTK_STATE_NORMAL],
1502                                          (down_p
1503                                           ? (gchar **) down_arrow_xpm
1504                                           : (gchar **) up_arrow_xpm));
1505   pixmapwid = gtk_pixmap_new (pixmap, mask);
1506   gtk_widget_show (pixmapwid);
1507   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
1508   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
1509 }
1510
1511 static void
1512 map_next_button_cb (GtkWidget *w, gpointer user_data)
1513 {
1514   pixmapify_button (w, 1);
1515 }
1516
1517 static void
1518 map_prev_button_cb (GtkWidget *w, gpointer user_data)
1519 {
1520   pixmapify_button (w, 0);
1521 }
1522
1523
1524 \f
1525 /* Work around a Gtk bug that causes label widgets to wrap text too early.
1526  */
1527
1528 static void
1529 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
1530                                              GtkAllocation *allocation,
1531                                              void *foo)
1532 {
1533   GtkRequisition req;
1534   GtkWidgetAuxInfo *aux_info;
1535
1536   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
1537
1538   aux_info->width = allocation->width;
1539   aux_info->height = -2;
1540   aux_info->x = -1;
1541   aux_info->y = -1;
1542
1543   gtk_widget_size_request (label, &req);
1544 }
1545
1546
1547 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
1548  */
1549 static void
1550 eschew_gtk_lossage (GtkWidget *toplevel)
1551 {
1552   GtkWidgetAuxInfo *aux_info;
1553   GtkWidget *label = GTK_WIDGET (name_to_widget (toplevel, "doc"));
1554
1555   aux_info = g_new0 (GtkWidgetAuxInfo, 1);
1556   aux_info->width = label->allocation.width;
1557   aux_info->height = -2;
1558   aux_info->x = -1;
1559   aux_info->y = -1;
1560
1561   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
1562
1563   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
1564                       you_are_not_a_unique_or_beautiful_snowflake, NULL);
1565
1566   gtk_widget_queue_resize (label); 
1567 }
1568
1569
1570 char *
1571 get_hack_blurb (screenhack *hack)
1572 {
1573   char *doc_string;
1574   char *prog_name = strdup (hack->command);
1575   char *pretty_name = (hack->name
1576                        ? strdup (hack->name)
1577                        : make_hack_name (hack->command));
1578   char doc_name[255], doc_class[255];
1579   char *s, *s2;
1580
1581   for (s = prog_name; *s && !isspace(*s); s++)
1582     ;
1583   *s = 0;
1584   s = strrchr (prog_name, '/');
1585   if (s) strcpy (prog_name, s+1);
1586
1587   sprintf (doc_name,  "hacks.%s.documentation", pretty_name);
1588   sprintf (doc_class, "hacks.%s.documentation", prog_name);
1589   free (prog_name);
1590   free (pretty_name);
1591
1592   doc_string = get_string_resource (doc_name, doc_class);
1593   if (doc_string)
1594     {
1595       for (s = doc_string; *s; s++)
1596         {
1597           if (*s == '\n')
1598             {
1599               /* skip over whitespace at beginning of line */
1600               s++;
1601               while (*s && (*s == ' ' || *s == '\t'))
1602                 s++;
1603             }
1604           else if (*s == ' ' || *s == '\t')
1605             {
1606               /* compress all other horizontal whitespace. */
1607               *s = ' ';
1608               s++;
1609               for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
1610                 ;
1611               if (s2 > s) strcpy (s, s2);
1612               s--;
1613             }
1614         }
1615
1616       while (*s && isspace (*s))      /* Strip trailing whitespace */
1617         *(--s) = 0;
1618
1619       /* Delete whitespace at end of each line. */
1620       for (; s > doc_string; s--)
1621         if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
1622           {
1623             for (s2 = s-1;
1624                  s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
1625                  s2--)
1626               ;
1627             s2++;
1628             if (s2 < s) strcpy (s2, s);
1629             s = s2;
1630           }
1631       
1632       /* Delete leading blank lines. */
1633       for (s = doc_string; *s == '\n'; s++)
1634         ;
1635       if (s > doc_string) strcpy (doc_string, s);
1636     }
1637   else
1638     {
1639       static int doc_installed = 0;
1640       if (doc_installed == 0)
1641         {
1642           if (get_boolean_resource ("hacks.documentation.isInstalled",
1643                                     "hacks.documentation.isInstalled"))
1644             doc_installed = 1;
1645           else
1646             doc_installed = -1;
1647         }
1648
1649       if (doc_installed < 0)
1650         doc_string =
1651           strdup ("Error:\n\n"
1652                   "The documentation strings do not appear to be "
1653                   "installed.  This is probably because there is "
1654                   "an \"XScreenSaver\" app-defaults file installed "
1655                   "that is from an older version of the program. "
1656                   "To fix this problem, delete that file, or "
1657                   "install a current version (either will work.)");
1658       else
1659         doc_string = strdup ("");
1660     }
1661
1662   return doc_string;
1663 }
1664
1665
1666 static void
1667 populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
1668 {
1669   saver_preferences *p = pair->a;
1670   screenhack *hack = (which >= 0 && which < p->screenhacks_count
1671                       ? p->screenhacks[which] : 0);
1672   GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame"));
1673   GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc"));
1674   GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text"));
1675   GtkToggleButton *enabled =
1676     GTK_TOGGLE_BUTTON (name_to_widget (toplevel, "enabled"));
1677   GtkCombo *vis = GTK_COMBO (name_to_widget (toplevel, "visual_combo"));
1678
1679   char *pretty_name = (hack
1680                        ? (hack->name
1681                           ? strdup (hack->name)
1682                           : make_hack_name (hack->command))
1683                        : 0);
1684   char *doc_string = hack ? get_hack_blurb (hack) : 0;
1685
1686   gtk_frame_set_label (frame, (pretty_name ? pretty_name : ""));
1687   gtk_label_set_text (doc, (doc_string ? doc_string : ""));
1688   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
1689   gtk_entry_set_position (cmd, 0);
1690
1691   updating_enabled_cb++;
1692   gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False));
1693   updating_enabled_cb--;
1694
1695   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
1696                       (hack
1697                        ? (hack->visual && *hack->visual
1698                           ? hack->visual
1699                           : "Any")
1700                        : ""));
1701
1702   gtk_container_resize_children (GTK_CONTAINER (GTK_WIDGET (doc)->parent));
1703
1704   sensitize_demo_widgets (toplevel, (hack ? True : False));
1705
1706   if (pretty_name) free (pretty_name);
1707   if (doc_string) free (doc_string);
1708
1709   _selected_hack_number = which;
1710 }
1711
1712
1713 static void
1714 widget_deleter (GtkWidget *widget, gpointer data)
1715 {
1716   /* #### Well, I want to destroy these widgets, but if I do that, they get
1717      referenced again, and eventually I get a SEGV.  So instead of
1718      destroying them, I'll just hide them, and leak a bunch of memory
1719      every time the disk file changes.  Go go go Gtk!
1720
1721      #### Ok, that's a lie, I get a crash even if I just hide the widget
1722      and don't ever delete it.  Fuck!
1723    */
1724 #if 0
1725   gtk_widget_destroy (widget);
1726 #else
1727   gtk_widget_hide (widget);
1728 #endif
1729 }
1730
1731
1732 static int
1733 maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair)
1734 {
1735   int status = 0;
1736   saver_preferences *p =  pair->a;
1737
1738   static Bool reentrant_lock = False;
1739   if (reentrant_lock) return 0;
1740   reentrant_lock = True;
1741
1742   if (init_file_changed_p (p))
1743     {
1744       const char *f = init_file_name();
1745       char *b;
1746       int which;
1747       GtkList *list;
1748
1749       if (!f || !*f) return 0;
1750       b = (char *) malloc (strlen(f) + 1024);
1751       sprintf (b,
1752                "Warning:\n\n"
1753                "file \"%s\" has changed, reloading.\n",
1754                f);
1755       warning_dialog (widget, b, False, 100);
1756       free (b);
1757
1758       load_init_file (p);
1759
1760       which = selected_hack_number (widget);
1761       list = GTK_LIST (name_to_widget (widget, "list"));
1762       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
1763       populate_hack_list (widget, pair);
1764       gtk_list_select_item (list, which);
1765       populate_prefs_page (widget, pair);
1766       populate_demo_window (widget, which, pair);
1767       ensure_selected_item_visible (GTK_WIDGET (list));
1768
1769       status = 1;
1770     }
1771
1772   reentrant_lock = False;
1773   return status;
1774 }
1775
1776
1777 \f
1778 /* Setting window manager icon
1779  */
1780
1781 #include "logo.xpm"
1782
1783 static void
1784 init_icon (GdkWindow *window)
1785 {
1786   GdkBitmap *mask = 0;
1787   GdkColor transp;
1788   GdkPixmap *pixmap =
1789     gdk_pixmap_create_from_xpm_d (window, &mask, &transp, logo_xpm);
1790   if (pixmap)
1791     gdk_window_set_icon (window, 0, pixmap, mask);
1792 }
1793
1794 \f
1795 /* The main demo-mode command loop.
1796  */
1797
1798 #if 0
1799 static Bool
1800 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1801         XrmRepresentation *type, XrmValue *value, XPointer closure)
1802 {
1803   int i;
1804   for (i = 0; quarks[i]; i++)
1805     {
1806       if (bindings[i] == XrmBindTightly)
1807         fprintf (stderr, (i == 0 ? "" : "."));
1808       else if (bindings[i] == XrmBindLoosely)
1809         fprintf (stderr, "*");
1810       else
1811         fprintf (stderr, " ??? ");
1812       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1813     }
1814
1815   fprintf (stderr, ": %s\n", (char *) value->addr);
1816
1817   return False;
1818 }
1819 #endif
1820
1821
1822 static void
1823 the_network_is_not_the_computer (GtkWidget *parent)
1824 {
1825   Display *dpy = gdk_display;
1826   char *rversion, *ruser, *rhost;
1827   char *luser, *lhost;
1828   char *msg = 0;
1829   struct passwd *p = getpwuid (getuid ());
1830   const char *d = DisplayString (dpy);
1831
1832 # if defined(HAVE_UNAME)
1833   struct utsname uts;
1834   if (uname (&uts) < 0)
1835     lhost = "<UNKNOWN>";
1836   else
1837     lhost = uts.nodename;
1838 # elif defined(VMS)
1839   strcpy (lhost, getenv("SYS$NODE"));
1840 # else  /* !HAVE_UNAME && !VMS */
1841   strcat (lhost, "<UNKNOWN>");
1842 # endif /* !HAVE_UNAME && !VMS */
1843
1844   if (p && p->pw_name)
1845     luser = p->pw_name;
1846   else
1847     luser = "???";
1848
1849   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1850
1851   /* Make a buffer that's big enough for a number of copies of all the
1852      strings, plus some. */
1853   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1854                                (ruser ? strlen(ruser) : 0) +
1855                                (rhost ? strlen(rhost) : 0) +
1856                                strlen(lhost) +
1857                                strlen(luser) +
1858                                strlen(d) +
1859                                1024));
1860   *msg = 0;
1861
1862   if (!rversion || !*rversion)
1863     {
1864       sprintf (msg,
1865                "Warning:\n\n"
1866                "The XScreenSaver daemon doesn't seem to be running\n"
1867                "on display \"%s\".  Launch it now?",
1868                d);
1869     }
1870   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1871     {
1872       /* Warn that the two processes are running as different users.
1873        */
1874       sprintf(msg,
1875                "Warning:\n\n"
1876               "%s is running as user \"%s\" on host \"%s\".\n"
1877               "But the xscreensaver managing display \"%s\"\n"
1878               "is running as user \"%s\" on host \"%s\".\n"
1879               "\n"
1880               "Since they are different users, they won't be reading/writing\n"
1881               "the same ~/.xscreensaver file, so %s isn't\n"
1882               "going to work right.\n"
1883               "\n"
1884               "You should either re-run %s as \"%s\", or re-run\n"
1885               "xscreensaver as \"%s\".\n"
1886               "\n"
1887               "Restart the xscreensaver daemon now?\n",
1888               progname, luser, lhost,
1889               d,
1890               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1891               progname,
1892               progname, (ruser ? ruser : "???"),
1893               luser);
1894     }
1895   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1896     {
1897       /* Warn that the two processes are running on different hosts.
1898        */
1899       sprintf (msg,
1900                "Warning:\n\n"
1901                "%s is running as user \"%s\" on host \"%s\".\n"
1902                "But the xscreensaver managing display \"%s\"\n"
1903                "is running as user \"%s\" on host \"%s\".\n"
1904                "\n"
1905                "If those two machines don't share a file system (that is,\n"
1906                "if they don't see the same ~%s/.xscreensaver file) then\n"
1907                "%s won't work right.\n"
1908                "\n"
1909                "Restart the daemon on \"%s\" as \"%s\" now?\n",
1910                progname, luser, lhost,
1911                d,
1912                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1913                luser,
1914                progname,
1915                lhost, luser);
1916     }
1917   else if (!!strcmp (rversion, short_version))
1918     {
1919       /* Warn that the version numbers don't match.
1920        */
1921       sprintf (msg,
1922                "Warning:\n\n"
1923                "This is %s version %s.\n"
1924                "But the xscreensaver managing display \"%s\"\n"
1925                "is version %s.  This could cause problems.\n"
1926               "\n"
1927               "Restart the xscreensaver daemon now?\n",
1928                progname, short_version,
1929                d,
1930                rversion);
1931     }
1932
1933
1934   if (*msg)
1935     warning_dialog (parent, msg, True, 1);
1936
1937   free (msg);
1938 }
1939
1940
1941 /* We use this error handler so that X errors are preceeded by the name
1942    of the program that generated them.
1943  */
1944 static int
1945 demo_ehandler (Display *dpy, XErrorEvent *error)
1946 {
1947   fprintf (stderr, "\nX error in %s:\n", progname);
1948   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
1949     exit (-1);
1950   else
1951     fprintf (stderr, " (nonfatal.)\n");
1952   return 0;
1953 }
1954
1955
1956 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
1957    of the program that generated them; and also that we can ignore one
1958    particular bogus error message that Gdk madly spews.
1959  */
1960 static void
1961 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
1962                const gchar *message, gpointer user_data)
1963 {
1964   /* Ignore the message "Got event for unknown window: 0x...".
1965      Apparently some events are coming in for the xscreensaver window
1966      (presumably reply events related to the ClientMessage) and Gdk
1967      feels the need to complain about them.  So, just suppress any
1968      messages that look like that one.
1969    */
1970   if (strstr (message, "unknown window"))
1971     return;
1972
1973   fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain,
1974            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
1975             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
1976             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
1977             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
1978             log_level == G_LOG_LEVEL_INFO     ? "info" :
1979             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
1980            message,
1981            ((!*message || message[strlen(message)-1] != '\n')
1982             ? "\n" : ""));
1983 }
1984
1985
1986 static char *defaults[] = {
1987 #include "XScreenSaver_ad.h"
1988  0
1989 };
1990
1991 #if 0
1992 #ifdef HAVE_CRAPPLET
1993 static struct poptOption crapplet_options[] = {
1994   {NULL, '\0', 0, NULL, 0}
1995 };
1996 #endif /* HAVE_CRAPPLET */
1997 #endif /* 0 */
1998
1999 #define USAGE() \
2000   fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", \
2001            real_progname)
2002
2003
2004 static void
2005 map_window_cb (GtkWidget *w, gpointer user_data)
2006 {
2007   Boolean oi = initializing_p;
2008   initializing_p = True;
2009   eschew_gtk_lossage (w);
2010   ensure_selected_item_visible (GTK_WIDGET(name_to_widget(w, "list")));
2011   initializing_p = oi;
2012 }
2013
2014
2015 int
2016 main (int argc, char **argv)
2017 {
2018   XtAppContext app;
2019   prefs_pair Pair, *pair;
2020   saver_preferences P, P2, *p, *p2;
2021   Bool prefs = False;
2022   int i;
2023   Display *dpy;
2024   Widget toplevel_shell;
2025   GtkWidget *gtk_window;
2026   char *real_progname = argv[0];
2027   char *s;
2028
2029   initializing_p = True;
2030
2031   s = strrchr (real_progname, '/');
2032   if (s) real_progname = s+1;
2033
2034   p = &P;
2035   p2 = &P2;
2036   pair = &Pair;
2037   pair->a = p;
2038   pair->b = p2;
2039   memset (p,  0, sizeof (*p));
2040   memset (p2, 0, sizeof (*p2));
2041
2042   global_prefs_pair = pair;  /* I hate C so much... */
2043
2044   progname = real_progname;
2045
2046   short_version = (char *) malloc (5);
2047   memcpy (short_version, screensaver_id + 17, 4);
2048   short_version [4] = 0;
2049
2050
2051   /* Register our error message logger for every ``log domain'' known.
2052      There's no way to do this globally, so I grepped the Gtk/Gdk sources
2053      for all of the domains that seem to be in use.
2054   */
2055   {
2056     const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule",
2057                                      "GThread", "Gnome", "GnomeUI", 0 };
2058     for (i = 0; domains[i]; i++)
2059       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
2060   }
2061
2062   /* This is gross, but Gtk understands --display and not -display...
2063    */
2064   for (i = 1; i < argc; i++)
2065     if (argv[i][0] && argv[i][1] && 
2066         !strncmp(argv[i], "-display", strlen(argv[i])))
2067       argv[i] = "--display";
2068
2069
2070   /* We need to parse this arg really early... Sigh. */
2071   for (i = 1; i < argc; i++)
2072     if (argv[i] &&
2073         (!strcmp(argv[i], "--crapplet") ||
2074          !strcmp(argv[i], "--capplet")))
2075       {
2076 # ifdef HAVE_CRAPPLET
2077         int j;
2078         crapplet_p = True;
2079         for (j = i; j < argc; j++)  /* remove it from the list */
2080           argv[j] = argv[j+1];
2081         argc--;
2082
2083 # else  /* !HAVE_CRAPPLET */
2084         fprintf (stderr, "%s: not compiled with --crapplet support\n",
2085                  real_progname[i]);
2086         USAGE ();
2087         exit (1);
2088 # endif /* !HAVE_CRAPPLET */
2089       }
2090
2091   /* Let Gtk open the X connection, then initialize Xt to use that
2092      same connection.  Doctor Frankenstein would be proud.
2093    */
2094 # ifdef HAVE_CRAPPLET
2095   if (crapplet_p)
2096     {
2097       GnomeClient *client;
2098       GnomeClientFlags flags = 0;
2099
2100       int init_results = gnome_capplet_init ("screensaver-properties",
2101                                              short_version,
2102                                              argc, argv, NULL, 0, NULL);
2103       /* init_results is:
2104          0 upon successful initialization;
2105          1 if --init-session-settings was passed on the cmdline;
2106          2 if --ignore was passed on the cmdline;
2107         -1 on error.
2108
2109          So the 1 signifies just to init the settings, and quit, basically.
2110          (Meaning launch the xscreensaver daemon.)
2111        */
2112
2113       if (init_results < 0)
2114         {
2115 #  if 0
2116           g_error ("An initialization error occurred while "
2117                    "starting xscreensaver-capplet.\n");
2118 #  else  /* !0 */
2119           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
2120                    real_progname, init_results);
2121           exit (1);
2122 #  endif /* !0 */
2123         }
2124
2125       client = gnome_master_client ();
2126
2127       if (client)
2128         flags = gnome_client_get_flags (client);
2129
2130       if (flags & GNOME_CLIENT_IS_CONNECTED)
2131         {
2132           int token =
2133             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
2134                                          gnome_client_get_id (client));
2135           if (token)
2136             {
2137               char *session_args[20];
2138               int i = 0;
2139               session_args[i++] = real_progname;
2140               session_args[i++] = "--capplet";
2141               session_args[i++] = "--init-session-settings";
2142               session_args[i] = 0;
2143               gnome_client_set_priority (client, 20);
2144               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
2145               gnome_client_set_restart_command (client, i, session_args);
2146             }
2147           else
2148             {
2149               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
2150             }
2151
2152           gnome_client_flush (client);
2153         }
2154
2155       if (init_results == 1)
2156         {
2157           system ("xscreensaver -nosplash &");
2158           return 0;
2159         }
2160
2161     }
2162   else
2163 # endif /* HAVE_CRAPPLET */
2164     {
2165       gtk_init (&argc, &argv);
2166     }
2167
2168
2169   /* We must read exactly the same resources as xscreensaver.
2170      That means we must have both the same progclass *and* progname,
2171      at least as far as the resource database is concerned.  So,
2172      put "xscreensaver" in argv[0] while initializing Xt.
2173    */
2174   argv[0] = "xscreensaver";
2175   progname = argv[0];
2176
2177
2178   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
2179    */
2180   XtToolkitInitialize ();
2181   app = XtCreateApplicationContext ();
2182   dpy = gdk_display;
2183   XtAppSetFallbackResources (app, defaults);
2184   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
2185   toplevel_shell = XtAppCreateShell (progname, progclass,
2186                                      applicationShellWidgetClass,
2187                                      dpy, 0, 0);
2188
2189   dpy = XtDisplay (toplevel_shell);
2190   db = XtDatabase (dpy);
2191   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
2192   XSetErrorHandler (demo_ehandler);
2193
2194
2195   /* After doing Xt-style command-line processing, complain about any
2196      unrecognized command-line arguments.
2197    */
2198   for (i = 1; i < argc; i++)
2199     {
2200       char *s = argv[i];
2201       if (s[0] == '-' && s[1] == '-')
2202         s++;
2203       if (!strcmp (s, "-prefs"))
2204         prefs = True;
2205       else if (crapplet_p)
2206         /* There are lots of random args that we don't care about when we're
2207            started as a crapplet, so just ignore unknown args in that case. */
2208         ;
2209       else
2210         {
2211           fprintf (stderr, "%s: unknown option: %s\n", real_progname, argv[i]);
2212           USAGE ();
2213           exit (1);
2214         }
2215     }
2216
2217   /* Load the init file, which may end up consulting the X resource database
2218      and the site-wide app-defaults file.  Note that at this point, it's
2219      important that `progname' be "xscreensaver", rather than whatever
2220      was in argv[0].
2221    */
2222   p->db = db;
2223   load_init_file (p);
2224   *p2 = *p;
2225
2226   /* Now that Xt has been initialized, and the resources have been read,
2227      we can set our `progname' variable to something more in line with
2228      reality.
2229    */
2230   progname = real_progname;
2231
2232
2233 #if 0
2234   /* Print out all the resources we read. */
2235   {
2236     XrmName name = { 0 };
2237     XrmClass class = { 0 };
2238     int count = 0;
2239     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
2240                           (POINTER) &count);
2241   }
2242 #endif
2243
2244
2245   /* Intern the atoms that xscreensaver_command() needs.
2246    */
2247   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
2248   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
2249   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
2250   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
2251   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
2252   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
2253   XA_SELECT = XInternAtom (dpy, "SELECT", False);
2254   XA_DEMO = XInternAtom (dpy, "DEMO", False);
2255   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
2256   XA_BLANK = XInternAtom (dpy, "BLANK", False);
2257   XA_LOCK = XInternAtom (dpy, "LOCK", False);
2258   XA_EXIT = XInternAtom (dpy, "EXIT", False);
2259   XA_RESTART = XInternAtom (dpy, "RESTART", False);
2260
2261
2262   /* Create the window and all its widgets.
2263    */
2264   gtk_window = create_xscreensaver_demo ();
2265   toplevel_widget = gtk_window;
2266
2267   /* Set the window's title. */
2268   {
2269     char title[255];
2270     char *v = (char *) strdup(strchr(screensaver_id, ' '));
2271     char *s1, *s2, *s3, *s4;
2272     s1 = (char *) strchr(v,  ' '); s1++;
2273     s2 = (char *) strchr(s1, ' ');
2274     s3 = (char *) strchr(v,  '('); s3++;
2275     s4 = (char *) strchr(s3, ')');
2276     *s2 = 0;
2277     *s4 = 0;
2278     sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
2279     gtk_window_set_title (GTK_WINDOW (gtk_window), title);
2280     free (v);
2281   }
2282
2283   /* Various other widget initializations...
2284    */
2285   gtk_signal_connect (GTK_OBJECT (gtk_window), "delete_event",
2286                       GTK_SIGNAL_FUNC (wm_close_cb), NULL);
2287
2288   populate_hack_list (gtk_window, pair);
2289   populate_prefs_page (gtk_window, pair);
2290   sensitize_demo_widgets (gtk_window, False);
2291   fix_text_entry_sizes (gtk_window);
2292   scroll_to_current_hack (gtk_window, pair);
2293
2294   gtk_signal_connect (
2295               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "list")),
2296               "map", GTK_SIGNAL_FUNC(map_window_cb), 0);
2297   gtk_signal_connect (
2298               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "prev")),
2299               "map", GTK_SIGNAL_FUNC(map_prev_button_cb), 0);
2300   gtk_signal_connect (
2301               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "next")),
2302               "map", GTK_SIGNAL_FUNC(map_next_button_cb), 0);
2303
2304
2305   /* Handle the -prefs command-line argument. */
2306   if (prefs)
2307     {
2308       GtkNotebook *notebook =
2309         GTK_NOTEBOOK (name_to_widget (gtk_window, "notebook"));
2310       gtk_notebook_set_page (notebook, 1);
2311     }
2312
2313 # ifdef HAVE_CRAPPLET
2314   if (crapplet_p)
2315     {
2316       GtkWidget *capplet;
2317       GtkWidget *top_vbox;
2318
2319       capplet = capplet_widget_new ();
2320
2321       top_vbox = GTK_BIN (gtk_window)->child;
2322
2323       gtk_widget_ref (top_vbox);
2324       gtk_container_remove (GTK_CONTAINER (gtk_window), top_vbox);
2325       GTK_OBJECT_SET_FLAGS (top_vbox, GTK_FLOATING);
2326
2327       /* In crapplet-mode, take off the menubar. */
2328       gtk_widget_hide (name_to_widget (gtk_window, "menubar"));
2329
2330       gtk_container_add (GTK_CONTAINER (capplet), top_vbox);
2331       gtk_widget_show (capplet);
2332       gtk_widget_hide (gtk_window);
2333
2334       /* Hook up the Control Center's redundant Help button, too. */
2335       gtk_signal_connect (GTK_OBJECT (capplet), "help",
2336                           GTK_SIGNAL_FUNC (doc_menu_cb), 0);
2337
2338       /* Issue any warnings about the running xscreensaver daemon. */
2339       the_network_is_not_the_computer (top_vbox);
2340     }
2341   else
2342 # endif /* HAVE_CRAPPLET */
2343     {
2344       gtk_widget_show (gtk_window);
2345       init_icon (GTK_WIDGET(gtk_window)->window);
2346
2347       /* Issue any warnings about the running xscreensaver daemon. */
2348       the_network_is_not_the_computer (gtk_window);
2349     }
2350
2351   /* Run the Gtk event loop, and not the Xt event loop.  This means that
2352      if there were Xt timers or fds registered, they would never get serviced,
2353      and if there were any Xt widgets, they would never have events delivered.
2354      Fortunately, we're using Gtk for all of the UI, and only initialized
2355      Xt so that we could process the command line and use the X resource
2356      manager.
2357    */
2358   initializing_p = False;
2359
2360 # ifdef HAVE_CRAPPLET
2361   if (crapplet_p)
2362     capplet_gtk_main ();
2363   else
2364 # endif /* HAVE_CRAPPLET */
2365     gtk_main ();
2366
2367   exit (0);
2368 }
2369
2370 #endif /* HAVE_GTK -- whole file */