1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2 * xscreensaver, Copyright (c) 1993-2005 Jamie Zawinski <jwz@jwz.org>
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
17 #ifdef HAVE_GTK /* whole file */
19 #include <xscreensaver-intl.h>
28 # define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */
36 #endif /* ENABLE_NLS */
39 # include <pwd.h> /* for getpwuid() */
45 # include <sys/utsname.h> /* for uname() */
46 #endif /* HAVE_UNAME */
56 #ifdef HAVE_SYS_WAIT_H
57 # include <sys/wait.h> /* for waitpid() and associated macros */
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>
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>
73 # include <X11/Xmu/Error.h>
75 # include <Xmu/Error.h>
82 # include <X11/extensions/Xinerama.h>
83 #endif /* HAVE_XINERAMA */
89 # include <capplet-widget.h>
95 # include <glade/glade-xml.h>
97 #else /* !HAVE_GTK2 */
98 # define G_MODULE_EXPORT /**/
99 #endif /* !HAVE_GTK2 */
101 #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
102 # define GLADE_DIR DEFAULT_ICONDIR
104 #if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
105 # define DEFAULT_ICONDIR GLADE_DIR
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;
118 #include "resources.h" /* for parse_time() */
119 #include "visual.h" /* for has_writable_cells() */
120 #include "remote.h" /* for xscreensaver_command() */
123 #include "logo-50.xpm"
124 #include "logo-180.xpm"
126 #undef dgettext /* else these are defined twice... */
129 #include "demo-Gtk-widgets.h"
130 #include "demo-Gtk-support.h"
131 #include "demo-Gtk-conf.h"
143 #endif /* HAVE_GTK2 */
146 extern void exec_command (const char *shell, const char *command, int nice);
148 static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
151 #define countof(x) (sizeof((x))/sizeof((*x)))
155 char *progclass = "XScreenSaver";
158 /* The order of the items in the mode menu. */
159 static int mode_menu_order[] = {
160 DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME };
165 char *short_version; /* version number of this xscreensaver build */
167 GtkWidget *toplevel_widget; /* the main window */
168 GtkWidget *base_widget; /* root of our hierarchy (for name lookups) */
169 GtkWidget *popup_widget; /* the "Settings" dialog */
170 conf_data *cdata; /* private data for per-hack configuration */
173 GladeXML *glade_ui; /* Glade UI file */
174 #endif /* HAVE_GTK2 */
176 Bool debug_p; /* whether to print diagnostics */
177 Bool initializing_p; /* flag for breaking recursion loops */
178 Bool saving_p; /* flag for breaking recursion loops */
180 char *desired_preview_cmd; /* subprocess we intend to run */
181 char *running_preview_cmd; /* subprocess we are currently running */
182 pid_t running_preview_pid; /* pid of forked subproc (might be dead) */
183 Bool running_preview_error_p; /* whether the pid died abnormally */
185 Bool preview_suppressed_p; /* flag meaning "don't launch subproc" */
186 int subproc_timer_id; /* timer to delay subproc launch */
187 int subproc_check_timer_id; /* timer to check whether it started up */
188 int subproc_check_countdown; /* how many more checks left */
190 int *list_elt_to_hack_number; /* table for sorting the hack list */
191 int *hack_number_to_list_elt; /* the inverse table */
192 Bool *hacks_available_p; /* whether hacks are on $PATH */
193 int total_available; /* how many are on $PATH */
194 int list_count; /* how many items are in the list: this may be
195 less than p->screenhacks_count, if some are
198 int _selected_list_element; /* don't use this: call
199 selected_list_element() instead */
201 int nscreens; /* How many X or Xinerama screens there are */
203 saver_preferences prefs;
208 /* Total fucking evilness due to the fact that it's rocket science to get
209 a closure object of our own down into the various widget callbacks. */
210 static state *global_state_kludge;
213 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
214 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
215 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
218 static void populate_demo_window (state *, int list_elt);
219 static void populate_prefs_page (state *);
220 static void populate_popup_window (state *);
222 static Bool flush_dialog_changes_and_save (state *);
223 static Bool flush_popup_changes_and_save (state *);
225 static int maybe_reload_init_file (state *);
226 static void await_xscreensaver (state *);
227 static Bool xscreensaver_running_p (state *);
228 static void sensitize_menu_items (state *s, Bool force_p);
229 static void force_dialog_repaint (state *s);
231 static void schedule_preview (state *, const char *cmd);
232 static void kill_preview_subproc (state *, Bool reset_p);
233 static void schedule_preview_check (state *);
237 /* Some random utility functions
243 time_t now = time ((time_t *) 0);
244 char *ct = (char *) ctime (&now);
245 static char buf[255];
246 int n = strlen(progname);
248 strncpy(buf, progname, n);
251 strncpy(buf+n, ct+11, 8);
252 strcpy(buf+n+9, ": ");
258 name_to_widget (state *s, const char *name)
268 /* First try to load the Glade file from the current directory;
269 if there isn't one there, check the installed directory.
271 # define GLADE_FILE_NAME "xscreensaver-demo.glade2"
272 const char * const files[] = { GLADE_FILE_NAME,
273 GLADE_DIR "/" GLADE_FILE_NAME };
275 for (i = 0; i < countof (files); i++)
278 if (!stat (files[i], &st))
280 s->glade_ui = glade_xml_new (files[i], NULL, NULL);
287 "%s: could not load \"" GLADE_FILE_NAME "\"\n"
288 "\tfrom " GLADE_DIR "/ or current directory.\n",
292 # undef GLADE_FILE_NAME
294 glade_xml_signal_autoconnect (s->glade_ui);
297 w = glade_xml_get_widget (s->glade_ui, name);
299 #else /* !HAVE_GTK2 */
301 w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
304 w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
306 #endif /* HAVE_GTK2 */
309 fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n",
315 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
316 Takes a scroller, viewport, or list as an argument.
319 ensure_selected_item_visible (GtkWidget *widget)
323 GtkTreeSelection *selection;
327 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
328 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
329 path = gtk_tree_path_new_first ();
331 path = gtk_tree_model_get_path (model, &iter);
333 gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
335 gtk_tree_path_free (path);
337 #else /* !HAVE_GTK2 */
339 GtkScrolledWindow *scroller = 0;
341 GtkList *list_widget = 0;
345 GtkWidget *selected = 0;
348 gint parent_h, child_y, child_h, children_h, ignore;
349 double ratio_t, ratio_b;
351 if (GTK_IS_SCROLLED_WINDOW (widget))
353 scroller = GTK_SCROLLED_WINDOW (widget);
354 vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
355 list_widget = GTK_LIST (GTK_BIN(vp)->child);
357 else if (GTK_IS_VIEWPORT (widget))
359 vp = GTK_VIEWPORT (widget);
360 scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
361 list_widget = GTK_LIST (GTK_BIN(vp)->child);
363 else if (GTK_IS_LIST (widget))
365 list_widget = GTK_LIST (widget);
366 vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
367 scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
372 slist = list_widget->selection;
373 selected = (slist ? GTK_WIDGET (slist->data) : 0);
377 list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
379 for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
380 kids; kids = kids->next)
383 adj = gtk_scrolled_window_get_vadjustment (scroller);
385 gdk_window_get_geometry (GTK_WIDGET(vp)->window,
386 &ignore, &ignore, &ignore, &parent_h, &ignore);
387 gdk_window_get_geometry (GTK_WIDGET(selected)->window,
388 &ignore, &child_y, &ignore, &child_h, &ignore);
389 children_h = nkids * child_h;
391 ratio_t = ((double) child_y) / ((double) children_h);
392 ratio_b = ((double) child_y + child_h) / ((double) children_h);
394 if (adj->upper == 0.0) /* no items in list */
397 if (ratio_t < (adj->value / adj->upper) ||
398 ratio_b > ((adj->value + adj->page_size) / adj->upper))
401 int slop = parent_h * 0.75; /* how much to overshoot by */
403 if (ratio_t < (adj->value / adj->upper))
405 double ratio_w = ((double) parent_h) / ((double) children_h);
406 double ratio_l = (ratio_b - ratio_t);
407 target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
410 else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
412 target = ratio_t * adj->upper;
416 if (target > adj->upper - adj->page_size)
417 target = adj->upper - adj->page_size;
421 gtk_adjustment_set_value (adj, target);
423 #endif /* !HAVE_GTK2 */
427 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
429 GtkWidget *shell = GTK_WIDGET (user_data);
430 while (shell->parent)
431 shell = shell->parent;
432 gtk_widget_destroy (GTK_WIDGET (shell));
436 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
438 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
440 restart_menu_cb (widget, user_data);
441 warning_dialog_dismiss_cb (widget, user_data);
445 warning_dialog (GtkWidget *parent, const char *message,
446 Boolean restart_button_p, int center)
448 char *msg = strdup (message);
451 GtkWidget *dialog = gtk_dialog_new ();
452 GtkWidget *label = 0;
454 GtkWidget *cancel = 0;
457 while (parent && !parent->window)
458 parent = parent->parent;
461 !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
463 fprintf (stderr, "%s: too early for dialog?\n", progname);
471 char *s = strchr (head, '\n');
474 sprintf (name, "label%d", i++);
477 label = gtk_label_new (head);
479 gtk_label_set_selectable (GTK_LABEL (label), TRUE);
480 #endif /* HAVE_GTK2 */
485 GTK_WIDGET (label)->style =
486 gtk_style_copy (GTK_WIDGET (label)->style);
487 GTK_WIDGET (label)->style->font =
488 gdk_font_load (get_string_resource("warning_dialog.headingFont",
490 gtk_widget_set_style (GTK_WIDGET (label),
491 GTK_WIDGET (label)->style);
493 #endif /* !HAVE_GTK2 */
495 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
496 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
497 label, TRUE, TRUE, 0);
498 gtk_widget_show (label);
509 label = gtk_label_new ("");
510 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
511 label, TRUE, TRUE, 0);
512 gtk_widget_show (label);
514 label = gtk_hbutton_box_new ();
515 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
516 label, TRUE, TRUE, 0);
519 if (restart_button_p)
521 cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
522 gtk_container_add (GTK_CONTAINER (label), cancel);
525 ok = gtk_button_new_from_stock (GTK_STOCK_OK);
526 gtk_container_add (GTK_CONTAINER (label), ok);
528 #else /* !HAVE_GTK2 */
530 ok = gtk_button_new_with_label ("OK");
531 gtk_container_add (GTK_CONTAINER (label), ok);
533 if (restart_button_p)
535 cancel = gtk_button_new_with_label ("Cancel");
536 gtk_container_add (GTK_CONTAINER (label), cancel);
539 #endif /* !HAVE_GTK2 */
541 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
542 gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
543 gtk_window_set_title (GTK_WINDOW (dialog), progclass);
544 STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
545 gtk_widget_show (ok);
546 gtk_widget_grab_focus (ok);
550 STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT);
551 gtk_widget_show (cancel);
553 gtk_widget_show (label);
554 gtk_widget_show (dialog);
556 if (restart_button_p)
558 gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
559 GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
561 gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
562 GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
567 gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
568 GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
572 gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
573 GTK_WIDGET (parent)->window);
576 gtk_window_present (GTK_WINDOW (dialog));
577 #else /* !HAVE_GTK2 */
578 gdk_window_show (GTK_WIDGET (dialog)->window);
579 gdk_window_raise (GTK_WIDGET (dialog)->window);
580 #endif /* !HAVE_GTK2 */
587 run_cmd (state *s, Atom command, int arg)
592 flush_dialog_changes_and_save (s);
593 status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
595 /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
596 if (status < 0 && err && strstr (err, "unexpectedly deleted"))
603 sprintf (buf, "Error:\n\n%s", err);
605 strcpy (buf, "Unknown error!");
606 warning_dialog (s->toplevel_widget, buf, False, 100);
610 sensitize_menu_items (s, True);
611 force_dialog_repaint (s);
616 run_hack (state *s, int list_elt, Bool report_errors_p)
622 if (list_elt < 0) return;
623 hack_number = s->list_elt_to_hack_number[list_elt];
625 flush_dialog_changes_and_save (s);
626 schedule_preview (s, 0);
628 status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
631 if (status < 0 && report_errors_p)
633 if (xscreensaver_running_p (s))
635 /* Kludge: ignore the spurious "window unexpectedly deleted"
637 if (err && strstr (err, "unexpectedly deleted"))
644 sprintf (buf, "Error:\n\n%s", err);
646 strcpy (buf, "Unknown error!");
647 warning_dialog (s->toplevel_widget, buf, False, 100);
652 /* The error is that the daemon isn't running;
655 const char *d = DisplayString (GDK_DISPLAY());
659 "The XScreenSaver daemon doesn't seem to be running\n"
660 "on display \"%s\". Launch it now?"),
662 warning_dialog (s->toplevel_widget, msg, True, 1);
668 sensitize_menu_items (s, False);
675 According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
676 libglade work on Cygwin; apparently all Glade callbacks need this magic
677 extra declaration. I do not pretend to understand.
681 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
683 state *s = global_state_kludge; /* I hate C so much... */
684 flush_dialog_changes_and_save (s);
685 kill_preview_subproc (s, False);
690 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
692 state *s = (state *) data;
693 flush_dialog_changes_and_save (s);
700 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
703 char *vers = strdup (screensaver_id + 4);
706 char *desc = _("For updates, check http://www.jwz.org/xscreensaver/");
708 s = strchr (vers, ',');
712 /* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
713 non-ASCII characters aren't allowed in localizable string keys."
714 (I don't want to just use (c) instead of © because that doesn't
715 look as good in the plain-old default Latin1 "C" locale.)
718 sprintf(copy, ("Copyright \xC2\xA9 1991-2005 %s"), s);
719 #else /* !HAVE_GTK2 */
720 sprintf(copy, ("Copyright \251 1991-2005 %s"), s);
721 #endif /* !HAVE_GTK2 */
723 sprintf (msg, "%s\n\n%s", copy, desc);
725 /* I can't make gnome_about_new() work here -- it starts dying in
726 gdk_imlib_get_visual() under gnome_about_new(). If this worked,
727 then this might be the thing to do:
731 const gchar *auth[] = { 0 };
732 GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
734 gtk_widget_show (about);
736 #else / * GTK but not GNOME * /
740 GdkColormap *colormap;
741 GdkPixmap *gdkpixmap;
744 GtkWidget *dialog = gtk_dialog_new ();
745 GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
746 GtkWidget *parent = GTK_WIDGET (menuitem);
747 while (parent->parent)
748 parent = parent->parent;
750 hbox = gtk_hbox_new (FALSE, 20);
751 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
752 hbox, TRUE, TRUE, 0);
754 colormap = gtk_widget_get_colormap (parent);
756 gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
757 (gchar **) logo_180_xpm);
758 icon = gtk_pixmap_new (gdkpixmap, mask);
759 gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
761 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
763 vbox = gtk_vbox_new (FALSE, 0);
764 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
766 label1 = gtk_label_new (vers);
767 gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
768 gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
769 gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
772 GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
773 GTK_WIDGET (label1)->style->font =
774 gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
775 gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
776 #endif /* HAVE_GTK2 */
778 label2 = gtk_label_new (msg);
779 gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
780 gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
781 gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
784 GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
785 GTK_WIDGET (label2)->style->font =
786 gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
787 gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
788 #endif /* HAVE_GTK2 */
790 hb = gtk_hbutton_box_new ();
792 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
796 ok = gtk_button_new_from_stock (GTK_STOCK_OK);
797 #else /* !HAVE_GTK2 */
798 ok = gtk_button_new_with_label (_("OK"));
799 #endif /* !HAVE_GTK2 */
800 gtk_container_add (GTK_CONTAINER (hb), ok);
802 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
803 gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
804 gtk_window_set_title (GTK_WINDOW (dialog), progclass);
806 gtk_widget_show (hbox);
807 gtk_widget_show (icon);
808 gtk_widget_show (vbox);
809 gtk_widget_show (label1);
810 gtk_widget_show (label2);
811 gtk_widget_show (hb);
812 gtk_widget_show (ok);
813 gtk_widget_show (dialog);
815 gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
816 GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
818 gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
819 GTK_WIDGET (parent)->window);
820 gdk_window_show (GTK_WIDGET (dialog)->window);
821 gdk_window_raise (GTK_WIDGET (dialog)->window);
827 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
829 state *s = global_state_kludge; /* I hate C so much... */
830 saver_preferences *p = &s->prefs;
833 if (!p->help_url || !*p->help_url)
835 warning_dialog (s->toplevel_widget,
837 "No Help URL has been specified.\n"), False, 100);
841 help_command = (char *) malloc (strlen (p->load_url_command) +
842 (strlen (p->help_url) * 4) + 20);
843 strcpy (help_command, "( ");
844 sprintf (help_command + strlen(help_command),
846 p->help_url, p->help_url, p->help_url, p->help_url);
847 strcat (help_command, " ) &");
848 if (system (help_command) < 0)
849 fprintf (stderr, "%s: fork error\n", blurb());
855 file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
857 state *s = global_state_kludge; /* I hate C so much... */
858 sensitize_menu_items (s, False);
863 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
865 state *s = global_state_kludge; /* I hate C so much... */
866 run_cmd (s, XA_ACTIVATE, 0);
871 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
873 state *s = global_state_kludge; /* I hate C so much... */
874 run_cmd (s, XA_LOCK, 0);
879 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
881 state *s = global_state_kludge; /* I hate C so much... */
882 run_cmd (s, XA_EXIT, 0);
887 restart_menu_cb (GtkWidget *widget, gpointer user_data)
889 state *s = global_state_kludge; /* I hate C so much... */
890 flush_dialog_changes_and_save (s);
891 xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
893 if (system ("xscreensaver -nosplash &") < 0)
894 fprintf (stderr, "%s: fork error\n", blurb());
896 await_xscreensaver (s);
900 xscreensaver_running_p (state *s)
902 Display *dpy = GDK_DISPLAY();
904 server_xscreensaver_version (dpy, &rversion, 0, 0);
912 await_xscreensaver (state *s)
917 while (!ok && (--countdown > 0))
918 if (xscreensaver_running_p (s))
921 sleep (1); /* If it's not there yet, wait a second... */
923 sensitize_menu_items (s, True);
927 /* Timed out, no screensaver running. */
930 Bool root_p = (geteuid () == 0);
934 "The xscreensaver daemon did not start up properly.\n"
940 __extension__ /* don't warn about "string length is greater than
941 the length ISO C89 compilers are required to
942 support" in the following expression... */
945 _("You are running as root. This usually means that xscreensaver\n"
946 "was unable to contact your X server because access control is\n"
947 "turned on. Try running this command:\n"
949 " xhost +localhost\n"
951 "and then selecting `File / Restart Daemon'.\n"
953 "Note that turning off access control will allow anyone logged\n"
954 "on to this machine to access your screen, which might be\n"
955 "considered a security problem. Please read the xscreensaver\n"
956 "manual and FAQ for more information.\n"
958 "You shouldn't run X as root. Instead, you should log in as a\n"
959 "normal user, and `su' as necessary."));
961 strcat (buf, _("Please check your $PATH and permissions."));
963 warning_dialog (s->toplevel_widget, buf, False, 1);
966 force_dialog_repaint (s);
971 selected_list_element (state *s)
973 return s->_selected_list_element;
978 demo_write_init_file (state *s, saver_preferences *p)
982 /* #### try to figure out why shit keeps getting reordered... */
983 if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow"))
987 if (!write_init_file (p, s->short_version, False))
990 fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
995 const char *f = init_file_name();
997 warning_dialog (s->toplevel_widget,
998 _("Error:\n\nCouldn't determine init file name!\n"),
1002 char *b = (char *) malloc (strlen(f) + 1024);
1003 sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
1004 warning_dialog (s->toplevel_widget, b, False, 100);
1012 G_MODULE_EXPORT void
1013 run_this_cb (GtkButton *button, gpointer user_data)
1015 state *s = global_state_kludge; /* I hate C so much... */
1016 int list_elt = selected_list_element (s);
1017 if (list_elt < 0) return;
1018 if (!flush_dialog_changes_and_save (s))
1019 run_hack (s, list_elt, True);
1023 G_MODULE_EXPORT void
1024 manual_cb (GtkButton *button, gpointer user_data)
1026 state *s = global_state_kludge; /* I hate C so much... */
1027 saver_preferences *p = &s->prefs;
1028 GtkWidget *list_widget = name_to_widget (s, "list");
1029 int list_elt = selected_list_element (s);
1031 char *name, *name2, *cmd, *str;
1032 if (list_elt < 0) return;
1033 hack_number = s->list_elt_to_hack_number[list_elt];
1035 flush_dialog_changes_and_save (s);
1036 ensure_selected_item_visible (list_widget);
1038 name = strdup (p->screenhacks[hack_number]->command);
1040 while (isspace (*name2)) name2++;
1042 while (*str && !isspace (*str)) str++;
1044 str = strrchr (name2, '/');
1045 if (str) name = str+1;
1047 cmd = get_string_resource ("manualCommand", "ManualCommand");
1050 char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
1051 strcpy (cmd2, "( ");
1052 sprintf (cmd2 + strlen (cmd2),
1054 name2, name2, name2, name2);
1055 strcat (cmd2, " ) &");
1056 if (system (cmd2) < 0)
1057 fprintf (stderr, "%s: fork error\n", blurb());
1062 warning_dialog (GTK_WIDGET (button),
1063 _("Error:\n\nno `manualCommand' resource set."),
1072 force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
1074 GtkWidget *parent = name_to_widget (s, "scroller");
1075 Bool was = GTK_WIDGET_IS_SENSITIVE (parent);
1078 GtkTreeModel *model;
1079 GtkTreeSelection *selection;
1080 #endif /* HAVE_GTK2 */
1082 if (!was) gtk_widget_set_sensitive (parent, True);
1084 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1085 STFU g_assert (model);
1086 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
1088 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
1089 gtk_tree_selection_select_iter (selection, &iter);
1091 #else /* !HAVE_GTK2 */
1092 gtk_list_select_item (GTK_LIST (list), list_elt);
1093 #endif /* !HAVE_GTK2 */
1094 if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
1095 if (!was) gtk_widget_set_sensitive (parent, False);
1099 G_MODULE_EXPORT void
1100 run_next_cb (GtkButton *button, gpointer user_data)
1102 state *s = global_state_kludge; /* I hate C so much... */
1103 /* saver_preferences *p = &s->prefs; */
1104 Bool ops = s->preview_suppressed_p;
1106 GtkWidget *list_widget = name_to_widget (s, "list");
1107 int list_elt = selected_list_element (s);
1114 if (list_elt >= s->list_count)
1117 s->preview_suppressed_p = True;
1119 flush_dialog_changes_and_save (s);
1120 force_list_select_item (s, list_widget, list_elt, True);
1121 populate_demo_window (s, list_elt);
1122 run_hack (s, list_elt, False);
1124 s->preview_suppressed_p = ops;
1128 G_MODULE_EXPORT void
1129 run_prev_cb (GtkButton *button, gpointer user_data)
1131 state *s = global_state_kludge; /* I hate C so much... */
1132 /* saver_preferences *p = &s->prefs; */
1133 Bool ops = s->preview_suppressed_p;
1135 GtkWidget *list_widget = name_to_widget (s, "list");
1136 int list_elt = selected_list_element (s);
1139 list_elt = s->list_count - 1;
1144 list_elt = s->list_count - 1;
1146 s->preview_suppressed_p = True;
1148 flush_dialog_changes_and_save (s);
1149 force_list_select_item (s, list_widget, list_elt, True);
1150 populate_demo_window (s, list_elt);
1151 run_hack (s, list_elt, False);
1153 s->preview_suppressed_p = ops;
1157 /* Writes the given settings into prefs.
1158 Returns true if there was a change, False otherwise.
1159 command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
1162 flush_changes (state *s,
1165 const char *command,
1168 saver_preferences *p = &s->prefs;
1169 Bool changed = False;
1172 if (list_elt < 0 || list_elt >= s->list_count)
1175 hack_number = s->list_elt_to_hack_number[list_elt];
1176 hack = p->screenhacks[hack_number];
1178 if (enabled_p != -1 &&
1179 enabled_p != hack->enabled_p)
1181 hack->enabled_p = enabled_p;
1184 fprintf (stderr, "%s: \"%s\": enabled => %d\n",
1185 blurb(), hack->name, enabled_p);
1190 if (!hack->command || !!strcmp (command, hack->command))
1192 if (hack->command) free (hack->command);
1193 hack->command = strdup (command);
1196 fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
1197 blurb(), hack->name, command);
1203 const char *ov = hack->visual;
1204 if (!ov || !*ov) ov = "any";
1205 if (!*visual) visual = "any";
1206 if (!!strcasecmp (visual, ov))
1208 if (hack->visual) free (hack->visual);
1209 hack->visual = strdup (visual);
1212 fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
1213 blurb(), hack->name, visual);
1221 /* Helper for the text fields that contain time specifications:
1222 this parses the text, and does error checking.
1225 hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
1230 if (!sec_p || strchr (line, ':'))
1231 value = parse_time ((char *) line, sec_p, True);
1235 if (sscanf (line, "%d%c", &value, &c) != 1)
1241 value *= 1000; /* Time measures in microseconds */
1247 "Unparsable time format: \"%s\"\n"),
1249 warning_dialog (s->toplevel_widget, b, False, 100);
1258 directory_p (const char *path)
1261 if (!path || !*path)
1263 else if (stat (path, &st))
1265 else if (!S_ISDIR (st.st_mode))
1272 file_p (const char *path)
1275 if (!path || !*path)
1277 else if (stat (path, &st))
1279 else if (S_ISDIR (st.st_mode))
1286 normalize_directory (const char *path)
1290 if (!path || !*path) return 0;
1292 p2 = (char *) malloc (L + 2);
1294 if (p2[L-1] == '/') /* remove trailing slash */
1297 for (s = p2; s && *s; s++)
1300 (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */
1301 !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */
1304 while (s0 > p2 && s0[-1] != '/')
1314 else if (*s == '/' && !strncmp (s, "/./", 3)) /* delete "/./" */
1315 strcpy (s, s+2), s--;
1316 else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */
1320 for (s = p2; s && *s; s++) /* normalize consecutive slashes */
1321 while (s[0] == '/' && s[1] == '/')
1324 /* and strip trailing whitespace for good measure. */
1326 while (isspace(p2[L-1]))
1339 } FlushForeachClosure;
1342 flush_checkbox (GtkTreeModel *model,
1347 FlushForeachClosure *closure = data;
1350 gtk_tree_model_get (model, iter,
1351 COL_ENABLED, &checked,
1354 if (flush_changes (closure->s, closure->i,
1356 *closure->changed = True;
1360 /* don't remove row */
1364 #endif /* HAVE_GTK2 */
1366 /* Flush out any changes made in the main dialog window (where changes
1367 take place immediately: clicking on a checkbox causes the init file
1368 to be written right away.)
1371 flush_dialog_changes_and_save (state *s)
1373 saver_preferences *p = &s->prefs;
1374 saver_preferences P2, *p2 = &P2;
1376 GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
1377 GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
1378 FlushForeachClosure closure;
1379 #else /* !HAVE_GTK2 */
1380 GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
1381 GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
1383 #endif /* !HAVE_GTK2 */
1385 Bool changed = False;
1388 if (s->saving_p) return False;
1393 /* Flush any checkbox changes in the list down into the prefs struct.
1397 closure.changed = &changed;
1399 gtk_tree_model_foreach (model, flush_checkbox, &closure);
1401 #else /* !HAVE_GTK2 */
1403 for (i = 0; kids; kids = kids->next, i++)
1405 GtkWidget *line = GTK_WIDGET (kids->data);
1406 GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1407 GtkWidget *line_check =
1408 GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1410 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
1412 if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
1415 #endif /* ~HAVE_GTK2 */
1417 /* Flush the non-hack-specific settings down into the prefs struct.
1420 # define SECONDS(FIELD,NAME) \
1421 w = name_to_widget (s, (NAME)); \
1422 hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
1424 # define MINUTES(FIELD,NAME) \
1425 w = name_to_widget (s, (NAME)); \
1426 hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
1428 # define CHECKBOX(FIELD,NAME) \
1429 w = name_to_widget (s, (NAME)); \
1430 (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
1432 # define PATHNAME(FIELD,NAME) \
1433 w = name_to_widget (s, (NAME)); \
1434 (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
1436 # define TEXT(FIELD,NAME) \
1437 w = name_to_widget (s, (NAME)); \
1438 (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w))
1440 MINUTES (&p2->timeout, "timeout_spinbutton");
1441 MINUTES (&p2->cycle, "cycle_spinbutton");
1442 CHECKBOX (p2->lock_p, "lock_button");
1443 MINUTES (&p2->lock_timeout, "lock_spinbutton");
1445 CHECKBOX (p2->dpms_enabled_p, "dpms_button");
1446 MINUTES (&p2->dpms_standby, "dpms_standby_spinbutton");
1447 MINUTES (&p2->dpms_suspend, "dpms_suspend_spinbutton");
1448 MINUTES (&p2->dpms_off, "dpms_off_spinbutton");
1450 CHECKBOX (p2->grab_desktop_p, "grab_desk_button");
1451 CHECKBOX (p2->grab_video_p, "grab_video_button");
1452 CHECKBOX (p2->random_image_p, "grab_image_button");
1453 PATHNAME (p2->image_directory, "image_text");
1456 CHECKBOX (p2->verbose_p, "verbose_button");
1457 CHECKBOX (p2->capture_stderr_p, "capture_button");
1458 CHECKBOX (p2->splash_p, "splash_button");
1463 CHECKBOX (v, "text_host_radio"); if (v) p2->tmode = TEXT_DATE;
1464 CHECKBOX (v, "text_radio"); if (v) p2->tmode = TEXT_LITERAL;
1465 CHECKBOX (v, "text_file_radio"); if (v) p2->tmode = TEXT_FILE;
1466 CHECKBOX (v, "text_program_radio"); if (v) p2->tmode = TEXT_PROGRAM;
1467 CHECKBOX (v, "text_url_radio"); if (v) p2->tmode = TEXT_URL;
1468 TEXT (p2->text_literal, "text_entry");
1469 PATHNAME (p2->text_file, "text_file_entry");
1470 PATHNAME (p2->text_program, "text_program_entry");
1471 PATHNAME (p2->text_program, "text_program_entry");
1472 TEXT (p2->text_url, "text_url_entry");
1475 CHECKBOX (p2->install_cmap_p, "install_button");
1476 CHECKBOX (p2->fade_p, "fade_button");
1477 CHECKBOX (p2->unfade_p, "unfade_button");
1478 SECONDS (&p2->fade_seconds, "fade_spinbutton");
1486 /* Warn if the image directory doesn't exist.
1488 if (p2->image_directory &&
1489 *p2->image_directory &&
1490 !directory_p (p2->image_directory))
1493 sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n",
1494 p2->image_directory);
1495 warning_dialog (s->toplevel_widget, b, False, 100);
1499 /* Map the mode menu to `saver_mode' enum values. */
1501 GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
1502 GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1503 GtkWidget *selected = gtk_menu_get_active (menu);
1504 GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1505 int menu_elt = g_list_index (kids, (gpointer) selected);
1506 if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
1507 p2->mode = mode_menu_order[menu_elt];
1510 if (p2->mode == ONE_HACK)
1512 int list_elt = selected_list_element (s);
1513 p2->selected_hack = (list_elt >= 0
1514 ? s->list_elt_to_hack_number[list_elt]
1518 # define COPY(field, name) \
1519 if (p->field != p2->field) { \
1522 fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
1524 p->field = p2->field
1527 COPY(selected_hack, "selected_hack");
1529 COPY(timeout, "timeout");
1530 COPY(cycle, "cycle");
1531 COPY(lock_p, "lock_p");
1532 COPY(lock_timeout, "lock_timeout");
1534 COPY(dpms_enabled_p, "dpms_enabled_p");
1535 COPY(dpms_standby, "dpms_standby");
1536 COPY(dpms_suspend, "dpms_suspend");
1537 COPY(dpms_off, "dpms_off");
1540 COPY(verbose_p, "verbose_p");
1541 COPY(capture_stderr_p, "capture_stderr_p");
1542 COPY(splash_p, "splash_p");
1545 COPY(tmode, "tmode");
1547 COPY(install_cmap_p, "install_cmap_p");
1548 COPY(fade_p, "fade_p");
1549 COPY(unfade_p, "unfade_p");
1550 COPY(fade_seconds, "fade_seconds");
1552 COPY(grab_desktop_p, "grab_desktop_p");
1553 COPY(grab_video_p, "grab_video_p");
1554 COPY(random_image_p, "random_image_p");
1558 # define COPYSTR(FIELD,NAME) \
1561 strcmp(p->FIELD, p2->FIELD)) \
1565 fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
1567 if (p->FIELD && p->FIELD != p2->FIELD) \
1569 p->FIELD = p2->FIELD; \
1572 COPYSTR(image_directory, "image_directory");
1573 COPYSTR(text_literal, "text_literal");
1574 COPYSTR(text_file, "text_file");
1575 COPYSTR(text_program, "text_program");
1576 COPYSTR(text_url, "text_url");
1579 populate_prefs_page (s);
1583 Display *dpy = GDK_DISPLAY();
1584 Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
1585 sync_server_dpms_settings (dpy, enabled_p,
1586 p->dpms_standby / 1000,
1587 p->dpms_suspend / 1000,
1591 changed = demo_write_init_file (s, p);
1594 s->saving_p = False;
1599 /* Flush out any changes made in the popup dialog box (where changes
1600 take place only when the OK button is clicked.)
1603 flush_popup_changes_and_save (state *s)
1605 Bool changed = False;
1606 saver_preferences *p = &s->prefs;
1607 int list_elt = selected_list_element (s);
1609 GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
1610 GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
1612 const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
1613 const char *command = gtk_entry_get_text (cmd);
1618 if (s->saving_p) return False;
1624 if (maybe_reload_init_file (s) != 0)
1630 /* Sanity-check and canonicalize whatever the user typed into the combo box.
1632 if (!strcasecmp (visual, "")) visual = "";
1633 else if (!strcasecmp (visual, "any")) visual = "";
1634 else if (!strcasecmp (visual, "default")) visual = "Default";
1635 else if (!strcasecmp (visual, "default-n")) visual = "Default-N";
1636 else if (!strcasecmp (visual, "default-i")) visual = "Default-I";
1637 else if (!strcasecmp (visual, "best")) visual = "Best";
1638 else if (!strcasecmp (visual, "mono")) visual = "Mono";
1639 else if (!strcasecmp (visual, "monochrome")) visual = "Mono";
1640 else if (!strcasecmp (visual, "gray")) visual = "Gray";
1641 else if (!strcasecmp (visual, "grey")) visual = "Gray";
1642 else if (!strcasecmp (visual, "color")) visual = "Color";
1643 else if (!strcasecmp (visual, "gl")) visual = "GL";
1644 else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray";
1645 else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor";
1646 else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor";
1647 else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale";
1648 else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale";
1649 else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor";
1650 else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor";
1651 else if (1 == sscanf (visual, " %lu %c", &id, &c)) ;
1652 else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
1655 gdk_beep (); /* unparsable */
1657 gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
1660 changed = flush_changes (s, list_elt, -1, command, visual);
1663 changed = demo_write_init_file (s, p);
1665 /* Do this to re-launch the hack if (and only if) the command line
1667 populate_demo_window (s, selected_list_element (s));
1671 s->saving_p = False;
1676 G_MODULE_EXPORT void
1677 pref_changed_cb (GtkWidget *widget, gpointer user_data)
1679 state *s = global_state_kludge; /* I hate C so much... */
1680 if (! s->initializing_p)
1682 s->initializing_p = True;
1683 flush_dialog_changes_and_save (s);
1684 s->initializing_p = False;
1688 G_MODULE_EXPORT gboolean
1689 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1691 pref_changed_cb (widget, user_data);
1695 /* Callback on menu items in the "mode" options menu.
1698 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
1700 state *s = (state *) user_data;
1701 saver_preferences *p = &s->prefs;
1702 GtkWidget *list = name_to_widget (s, "list");
1705 GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent));
1707 saver_mode new_mode;
1711 if (menu_items->data == widget)
1714 menu_items = menu_items->next;
1716 if (!menu_items) abort();
1718 new_mode = mode_menu_order[menu_index];
1720 /* Keep the same list element displayed as before; except if we're
1721 switching *to* "one screensaver" mode from any other mode, set
1722 "the one" to be that which is currently selected.
1724 list_elt = selected_list_element (s);
1725 if (new_mode == ONE_HACK)
1726 p->selected_hack = s->list_elt_to_hack_number[list_elt];
1729 saver_mode old_mode = p->mode;
1731 populate_demo_window (s, list_elt);
1732 force_list_select_item (s, list, list_elt, True);
1733 p->mode = old_mode; /* put it back, so the init file gets written */
1736 pref_changed_cb (widget, user_data);
1740 G_MODULE_EXPORT void
1741 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1742 gint page_num, gpointer user_data)
1744 state *s = global_state_kludge; /* I hate C so much... */
1745 pref_changed_cb (GTK_WIDGET (notebook), user_data);
1747 /* If we're switching to page 0, schedule the current hack to be run.
1748 Otherwise, schedule it to stop. */
1750 populate_demo_window (s, selected_list_element (s));
1752 schedule_preview (s, 0);
1757 list_activated_cb (GtkTreeView *list,
1759 GtkTreeViewColumn *column,
1766 STFU g_return_if_fail (!gdk_pointer_is_grabbed ());
1768 str = gtk_tree_path_to_string (path);
1769 list_elt = strtol (str, NULL, 10);
1773 run_hack (s, list_elt, True);
1777 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
1779 state *s = (state *)data;
1780 GtkTreeModel *model;
1786 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1789 path = gtk_tree_model_get_path (model, &iter);
1790 str = gtk_tree_path_to_string (path);
1791 list_elt = strtol (str, NULL, 10);
1793 gtk_tree_path_free (path);
1796 populate_demo_window (s, list_elt);
1797 flush_dialog_changes_and_save (s);
1799 /* Re-populate the Settings window any time a new item is selected
1800 in the list, in case both windows are currently visible.
1802 populate_popup_window (s);
1805 #else /* !HAVE_GTK2 */
1807 static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the
1808 list_select_cb that comes in
1809 *after* we've double-clicked.
1813 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1816 state *s = (state *) data;
1817 if (event->type == GDK_2BUTTON_PRESS)
1819 GtkList *list = GTK_LIST (name_to_widget (s, "list"));
1820 int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
1822 last_doubleclick_time = time ((time_t *) 0);
1825 run_hack (s, list_elt, True);
1833 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
1835 state *s = (state *) data;
1836 time_t now = time ((time_t *) 0);
1838 if (now >= last_doubleclick_time + 2)
1840 int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
1841 populate_demo_window (s, list_elt);
1842 flush_dialog_changes_and_save (s);
1847 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
1849 state *s = (state *) data;
1850 populate_demo_window (s, -1);
1851 flush_dialog_changes_and_save (s);
1854 #endif /* !HAVE_GTK2 */
1857 /* Called when the checkboxes that are in the left column of the
1858 scrolling list are clicked. This both populates the right pane
1859 (just as clicking on the label (really, listitem) does) and
1860 also syncs this checkbox with the right pane Enabled checkbox.
1865 GtkCellRendererToggle *toggle,
1867 #else /* !HAVE_GTK2 */
1869 #endif /* !HAVE_GTK2 */
1872 state *s = (state *) data;
1875 GtkScrolledWindow *scroller =
1876 GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
1877 GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
1878 GtkTreeModel *model = gtk_tree_view_get_model (list);
1879 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1882 #else /* !HAVE_GTK2 */
1883 GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
1884 GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
1886 GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
1887 GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
1888 GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
1889 #endif /* !HAVE_GTK2 */
1896 if (!gtk_tree_model_get_iter (model, &iter, path))
1898 g_warning ("bad path: %s", path_string);
1901 gtk_tree_path_free (path);
1903 gtk_tree_model_get (model, &iter,
1904 COL_ENABLED, &active,
1907 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1908 COL_ENABLED, !active,
1911 list_elt = strtol (path_string, NULL, 10);
1912 #else /* !HAVE_GTK2 */
1913 list_elt = gtk_list_child_position (list, line);
1914 #endif /* !HAVE_GTK2 */
1916 /* remember previous scroll position of the top of the list */
1917 adj = gtk_scrolled_window_get_vadjustment (scroller);
1918 scroll_top = adj->value;
1920 flush_dialog_changes_and_save (s);
1921 force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
1922 populate_demo_window (s, list_elt);
1924 /* restore the previous scroll position of the top of the list.
1925 this is weak, but I don't really know why it's moving... */
1926 gtk_adjustment_set_value (adj, scroll_top);
1932 GtkFileSelection *widget;
1933 } file_selection_data;
1938 store_image_directory (GtkWidget *button, gpointer user_data)
1940 file_selection_data *fsd = (file_selection_data *) user_data;
1941 state *s = fsd->state;
1942 GtkFileSelection *selector = fsd->widget;
1943 GtkWidget *top = s->toplevel_widget;
1944 saver_preferences *p = &s->prefs;
1945 const char *path = gtk_file_selection_get_filename (selector);
1947 if (p->image_directory && !strcmp(p->image_directory, path))
1948 return; /* no change */
1950 if (!directory_p (path))
1953 sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
1954 warning_dialog (GTK_WIDGET (top), b, False, 100);
1958 if (p->image_directory) free (p->image_directory);
1959 p->image_directory = normalize_directory (path);
1961 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
1962 (p->image_directory ? p->image_directory : ""));
1963 demo_write_init_file (s, p);
1968 store_text_file (GtkWidget *button, gpointer user_data)
1970 file_selection_data *fsd = (file_selection_data *) user_data;
1971 state *s = fsd->state;
1972 GtkFileSelection *selector = fsd->widget;
1973 GtkWidget *top = s->toplevel_widget;
1974 saver_preferences *p = &s->prefs;
1975 const char *path = gtk_file_selection_get_filename (selector);
1977 if (p->text_file && !strcmp(p->text_file, path))
1978 return; /* no change */
1983 sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
1984 warning_dialog (GTK_WIDGET (top), b, False, 100);
1988 if (p->text_file) free (p->text_file);
1989 p->text_file = normalize_directory (path);
1991 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
1992 (p->text_file ? p->text_file : ""));
1993 demo_write_init_file (s, p);
1998 store_text_program (GtkWidget *button, gpointer user_data)
2000 file_selection_data *fsd = (file_selection_data *) user_data;
2001 state *s = fsd->state;
2002 GtkFileSelection *selector = fsd->widget;
2003 /*GtkWidget *top = s->toplevel_widget;*/
2004 saver_preferences *p = &s->prefs;
2005 const char *path = gtk_file_selection_get_filename (selector);
2007 if (p->text_program && !strcmp(p->text_program, path))
2008 return; /* no change */
2014 sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2015 warning_dialog (GTK_WIDGET (top), b, False, 100);
2020 if (p->text_program) free (p->text_program);
2021 p->text_program = normalize_directory (path);
2023 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2024 (p->text_program ? p->text_program : ""));
2025 demo_write_init_file (s, p);
2031 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
2033 file_selection_data *fsd = (file_selection_data *) user_data;
2034 gtk_widget_hide (GTK_WIDGET (fsd->widget));
2038 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
2040 browse_image_dir_cancel (button, user_data);
2041 store_image_directory (button, user_data);
2045 browse_text_file_ok (GtkWidget *button, gpointer user_data)
2047 browse_image_dir_cancel (button, user_data);
2048 store_text_file (button, user_data);
2052 browse_text_program_ok (GtkWidget *button, gpointer user_data)
2054 browse_image_dir_cancel (button, user_data);
2055 store_text_program (button, user_data);
2059 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
2061 browse_image_dir_cancel (widget, user_data);
2065 G_MODULE_EXPORT void
2066 browse_image_dir_cb (GtkButton *button, gpointer user_data)
2068 state *s = global_state_kludge; /* I hate C so much... */
2069 saver_preferences *p = &s->prefs;
2070 static file_selection_data *fsd = 0;
2072 GtkFileSelection *selector = GTK_FILE_SELECTION(
2073 gtk_file_selection_new ("Please select the image directory."));
2076 fsd = (file_selection_data *) malloc (sizeof (*fsd));
2078 fsd->widget = selector;
2081 if (p->image_directory && *p->image_directory)
2082 gtk_file_selection_set_filename (selector, p->image_directory);
2084 gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2085 "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
2087 gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2088 "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2090 gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2091 GTK_SIGNAL_FUNC (browse_image_dir_close),
2094 gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
2096 gtk_window_set_modal (GTK_WINDOW (selector), True);
2097 gtk_widget_show (GTK_WIDGET (selector));
2101 G_MODULE_EXPORT void
2102 browse_text_file_cb (GtkButton *button, gpointer user_data)
2104 state *s = global_state_kludge; /* I hate C so much... */
2105 saver_preferences *p = &s->prefs;
2106 static file_selection_data *fsd = 0;
2108 GtkFileSelection *selector = GTK_FILE_SELECTION(
2109 gtk_file_selection_new ("Please select a text file."));
2112 fsd = (file_selection_data *) malloc (sizeof (*fsd));
2114 fsd->widget = selector;
2117 if (p->text_file && *p->text_file)
2118 gtk_file_selection_set_filename (selector, p->text_file);
2120 gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2121 "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
2123 gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2124 "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2126 gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2127 GTK_SIGNAL_FUNC (browse_image_dir_close),
2130 gtk_window_set_modal (GTK_WINDOW (selector), True);
2131 gtk_widget_show (GTK_WIDGET (selector));
2135 G_MODULE_EXPORT void
2136 browse_text_program_cb (GtkButton *button, gpointer user_data)
2138 state *s = global_state_kludge; /* I hate C so much... */
2139 saver_preferences *p = &s->prefs;
2140 static file_selection_data *fsd = 0;
2142 GtkFileSelection *selector = GTK_FILE_SELECTION(
2143 gtk_file_selection_new ("Please select a text-generating program."));
2146 fsd = (file_selection_data *) malloc (sizeof (*fsd));
2148 fsd->widget = selector;
2151 if (p->text_program && *p->text_program)
2152 gtk_file_selection_set_filename (selector, p->text_program);
2154 gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2155 "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
2157 gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2158 "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2160 gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2161 GTK_SIGNAL_FUNC (browse_image_dir_close),
2164 gtk_window_set_modal (GTK_WINDOW (selector), True);
2165 gtk_widget_show (GTK_WIDGET (selector));
2172 G_MODULE_EXPORT void
2173 settings_cb (GtkButton *button, gpointer user_data)
2175 state *s = global_state_kludge; /* I hate C so much... */
2176 int list_elt = selected_list_element (s);
2178 populate_demo_window (s, list_elt); /* reset the widget */
2179 populate_popup_window (s); /* create UI on popup window */
2180 gtk_widget_show (s->popup_widget);
2184 settings_sync_cmd_text (state *s)
2187 GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2188 char *cmd_line = get_configurator_command_line (s->cdata);
2189 gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2190 gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2192 # endif /* HAVE_XML */
2195 G_MODULE_EXPORT void
2196 settings_adv_cb (GtkButton *button, gpointer user_data)
2198 state *s = global_state_kludge; /* I hate C so much... */
2199 GtkNotebook *notebook =
2200 GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2202 settings_sync_cmd_text (s);
2203 gtk_notebook_set_page (notebook, 1);
2206 G_MODULE_EXPORT void
2207 settings_std_cb (GtkButton *button, gpointer user_data)
2209 state *s = global_state_kludge; /* I hate C so much... */
2210 GtkNotebook *notebook =
2211 GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2213 /* Re-create UI to reflect the in-progress command-line settings. */
2214 populate_popup_window (s);
2216 gtk_notebook_set_page (notebook, 0);
2219 G_MODULE_EXPORT void
2220 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
2221 gint page_num, gpointer user_data)
2223 state *s = global_state_kludge; /* I hate C so much... */
2224 GtkWidget *adv = name_to_widget (s, "adv_button");
2225 GtkWidget *std = name_to_widget (s, "std_button");
2229 gtk_widget_show (adv);
2230 gtk_widget_hide (std);
2232 else if (page_num == 1)
2234 gtk_widget_hide (adv);
2235 gtk_widget_show (std);
2243 G_MODULE_EXPORT void
2244 settings_cancel_cb (GtkButton *button, gpointer user_data)
2246 state *s = global_state_kludge; /* I hate C so much... */
2247 gtk_widget_hide (s->popup_widget);
2250 G_MODULE_EXPORT void
2251 settings_ok_cb (GtkButton *button, gpointer user_data)
2253 state *s = global_state_kludge; /* I hate C so much... */
2254 GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2255 int page = gtk_notebook_get_current_page (notebook);
2258 /* Regenerate the command-line from the widget contents before saving.
2259 But don't do this if we're looking at the command-line page already,
2260 or we will blow away what they typed... */
2261 settings_sync_cmd_text (s);
2263 flush_popup_changes_and_save (s);
2264 gtk_widget_hide (s->popup_widget);
2268 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
2270 state *s = (state *) data;
2271 settings_cancel_cb (0, (gpointer) s);
2277 /* Populating the various widgets
2281 /* Returns the number of the last hack run by the server.
2284 server_current_hack (void)
2288 unsigned long nitems, bytesafter;
2289 unsigned char *dataP = 0;
2290 Display *dpy = GDK_DISPLAY();
2291 int hack_number = -1;
2293 if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
2294 XA_SCREENSAVER_STATUS,
2295 0, 3, False, XA_INTEGER,
2296 &type, &format, &nitems, &bytesafter,
2299 && type == XA_INTEGER
2303 CARD32 *data = (CARD32 *) dataP;
2304 hack_number = (int) data[2] - 1;
2307 if (dataP) XFree (dataP);
2313 /* Finds the number of the last hack that was run, and makes that item be
2314 selected by default.
2317 scroll_to_current_hack (state *s)
2319 saver_preferences *p = &s->prefs;
2320 int hack_number = -1;
2322 if (p->mode == ONE_HACK) /* in "one" mode, use the one */
2323 hack_number = p->selected_hack;
2324 if (hack_number < 0) /* otherwise, use the last-run */
2325 hack_number = server_current_hack ();
2326 if (hack_number < 0) /* failing that, last "one mode" */
2327 hack_number = p->selected_hack;
2328 if (hack_number < 0) /* failing that, newest hack. */
2330 /* We should only get here if the user does not have a .xscreensaver
2331 file, and the screen has not been blanked with a hack since X
2332 started up: in other words, this is probably a fresh install.
2334 Instead of just defaulting to hack #0 (in either "programs" or
2335 "alphabetical" order) let's try to default to the last runnable
2336 hack in the "programs" list: this is probably the hack that was
2337 most recently added to the xscreensaver distribution (and so
2338 it's probably the currently-coolest one!)
2340 hack_number = p->screenhacks_count-1;
2341 while (hack_number > 0 &&
2342 ! (s->hacks_available_p[hack_number] &&
2343 p->screenhacks[hack_number]->enabled_p))
2347 if (hack_number >= 0 && hack_number < p->screenhacks_count)
2349 int list_elt = s->hack_number_to_list_elt[hack_number];
2350 GtkWidget *list = name_to_widget (s, "list");
2351 force_list_select_item (s, list, list_elt, True);
2352 populate_demo_window (s, list_elt);
2358 on_path_p (const char *program)
2362 char *cmd = strdup (program);
2363 char *token = strchr (cmd, ' ');
2367 if (token) *token = 0;
2370 if (strchr (cmd, '/'))
2372 result = (0 == stat (cmd, &st));
2376 path = getenv("PATH");
2377 if (!path || !*path)
2381 path = strdup (path);
2382 token = strtok (path, ":");
2386 char *p2 = (char *) malloc (strlen (token) + L + 3);
2390 result = (0 == stat (p2, &st));
2393 token = strtok (0, ":");
2398 if (path) free (path);
2404 populate_hack_list (state *s)
2407 saver_preferences *p = &s->prefs;
2408 GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2409 GtkListStore *model;
2410 GtkTreeSelection *selection;
2411 GtkCellRenderer *ren;
2415 g_object_get (G_OBJECT (list),
2420 model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
2421 g_object_set (G_OBJECT (list), "model", model, NULL);
2422 g_object_unref (model);
2424 ren = gtk_cell_renderer_toggle_new ();
2425 gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
2427 "active", COL_ENABLED,
2430 g_signal_connect (ren, "toggled",
2431 G_CALLBACK (list_checkbox_cb),
2434 ren = gtk_cell_renderer_text_new ();
2435 gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
2436 _("Screen Saver"), ren,
2440 g_signal_connect_after (list, "row_activated",
2441 G_CALLBACK (list_activated_cb),
2444 selection = gtk_tree_view_get_selection (list);
2445 g_signal_connect (selection, "changed",
2446 G_CALLBACK (list_select_changed_cb),
2451 for (i = 0; i < s->list_count; i++)
2453 int hack_number = s->list_elt_to_hack_number[i];
2454 screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2456 Bool available_p = (hack && s->hacks_available_p [hack_number]);
2458 if (!hack) continue;
2460 /* If we're to suppress uninstalled hacks, check $PATH now. */
2461 if (p->ignore_uninstalled_p && !available_p)
2464 pretty_name = (hack->name
2465 ? strdup (hack->name)
2466 : make_hack_name (hack->command));
2470 /* Make the text foreground be the color of insensitive widgets
2471 (but don't actually make it be insensitive, since we still
2472 want to be able to click on it.)
2474 GtkStyle *style = GTK_WIDGET (list)->style;
2475 GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
2476 /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
2477 char *buf = (char *) malloc (strlen (pretty_name) + 100);
2479 sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
2480 /* " background=\"#%02X%02X%02X\"" */
2482 fg->red >> 8, fg->green >> 8, fg->blue >> 8,
2483 /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
2489 gtk_list_store_append (model, &iter);
2490 gtk_list_store_set (model, &iter,
2491 COL_ENABLED, hack->enabled_p,
2492 COL_NAME, pretty_name,
2497 #else /* !HAVE_GTK2 */
2499 saver_preferences *p = &s->prefs;
2500 GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2502 for (i = 0; i < s->list_count; i++)
2504 int hack_number = s->list_elt_to_hack_number[i];
2505 screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2507 /* A GtkList must contain only GtkListItems, but those can contain
2508 an arbitrary widget. We add an Hbox, and inside that, a Checkbox
2509 and a Label. We handle single and double click events on the
2510 line itself, for clicking on the text, but the interior checkbox
2511 also handles its own events.
2514 GtkWidget *line_hbox;
2515 GtkWidget *line_check;
2516 GtkWidget *line_label;
2518 Bool available_p = (hack && s->hacks_available_p [hack_number]);
2520 if (!hack) continue;
2522 /* If we're to suppress uninstalled hacks, check $PATH now. */
2523 if (p->ignore_uninstalled_p && !available_p)
2526 pretty_name = (hack->name
2527 ? strdup (hack->name)
2528 : make_hack_name (hack->command));
2530 line = gtk_list_item_new ();
2531 line_hbox = gtk_hbox_new (FALSE, 0);
2532 line_check = gtk_check_button_new ();
2533 line_label = gtk_label_new (pretty_name);
2535 gtk_container_add (GTK_CONTAINER (line), line_hbox);
2536 gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
2537 gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
2539 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
2541 gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
2543 gtk_widget_show (line_check);
2544 gtk_widget_show (line_label);
2545 gtk_widget_show (line_hbox);
2546 gtk_widget_show (line);
2550 gtk_container_add (GTK_CONTAINER (list), line);
2551 gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
2552 GTK_SIGNAL_FUNC (list_doubleclick_cb),
2555 gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
2556 GTK_SIGNAL_FUNC (list_checkbox_cb),
2559 gtk_widget_show (line);
2563 /* Make the widget be colored like insensitive widgets
2564 (but don't actually make it be insensitive, since we
2565 still want to be able to click on it.)
2567 GtkRcStyle *rc_style;
2570 gtk_widget_realize (GTK_WIDGET (line_label));
2572 fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
2573 bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
2575 rc_style = gtk_rc_style_new ();
2576 rc_style->fg[GTK_STATE_NORMAL] = fg;
2577 rc_style->bg[GTK_STATE_NORMAL] = bg;
2578 rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
2580 gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
2581 gtk_rc_style_unref (rc_style);
2585 gtk_signal_connect (GTK_OBJECT (list), "select_child",
2586 GTK_SIGNAL_FUNC (list_select_cb),
2588 gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
2589 GTK_SIGNAL_FUNC (list_unselect_cb),
2591 #endif /* !HAVE_GTK2 */
2595 update_list_sensitivity (state *s)
2597 saver_preferences *p = &s->prefs;
2598 Bool sensitive = (p->mode == RANDOM_HACKS ||
2599 p->mode == RANDOM_HACKS_SAME ||
2600 p->mode == ONE_HACK);
2601 Bool checkable = (p->mode == RANDOM_HACKS ||
2602 p->mode == RANDOM_HACKS_SAME);
2603 Bool blankable = (p->mode != DONT_BLANK);
2606 GtkWidget *head = name_to_widget (s, "col_head_hbox");
2607 GtkWidget *use = name_to_widget (s, "use_col_frame");
2608 #endif /* HAVE_GTK2 */
2609 GtkWidget *scroller = name_to_widget (s, "scroller");
2610 GtkWidget *buttons = name_to_widget (s, "next_prev_hbox");
2611 GtkWidget *blanker = name_to_widget (s, "blanking_table");
2614 GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2615 GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
2616 #else /* !HAVE_GTK2 */
2617 GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2618 GList *kids = gtk_container_children (GTK_CONTAINER (list));
2620 gtk_widget_set_sensitive (GTK_WIDGET (head), sensitive);
2621 #endif /* !HAVE_GTK2 */
2622 gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
2623 gtk_widget_set_sensitive (GTK_WIDGET (buttons), sensitive);
2625 gtk_widget_set_sensitive (GTK_WIDGET (blanker), blankable);
2628 gtk_tree_view_column_set_visible (use, checkable);
2629 #else /* !HAVE_GTK2 */
2631 gtk_widget_show (use); /* the "Use" column header */
2633 gtk_widget_hide (use);
2637 GtkBin *line = GTK_BIN (kids->data);
2638 GtkContainer *line_hbox = GTK_CONTAINER (line->child);
2639 GtkWidget *line_check =
2640 GTK_WIDGET (gtk_container_children (line_hbox)->data);
2643 gtk_widget_show (line_check);
2645 gtk_widget_hide (line_check);
2649 #endif /* !HAVE_GTK2 */
2654 populate_prefs_page (state *s)
2656 saver_preferences *p = &s->prefs;
2658 Bool can_lock_p = True;
2660 /* Disable all the "lock" controls if locking support was not provided
2661 at compile-time, or if running on MacOS. */
2662 # if defined(NO_LOCKING) || defined(__APPLE__)
2667 /* If there is only one screen, the mode menu contains
2668 "random" but not "random-same".
2670 if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
2671 p->mode = RANDOM_HACKS;
2674 /* The file supports timeouts of less than a minute, but the GUI does
2675 not, so throttle the values to be at least one minute (since "0" is
2676 a bad rounding choice...)
2678 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2681 /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */
2684 # define FMT_MINUTES(NAME,N) \
2685 gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2687 # define FMT_SECONDS(NAME,N) \
2688 gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2690 FMT_MINUTES ("timeout_spinbutton", p->timeout);
2691 FMT_MINUTES ("cycle_spinbutton", p->cycle);
2692 FMT_MINUTES ("lock_spinbutton", p->lock_timeout);
2693 FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2694 FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2695 FMT_MINUTES ("dpms_off_spinbutton", p->dpms_off);
2696 FMT_SECONDS ("fade_spinbutton", p->fade_seconds);
2701 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2702 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2705 TOGGLE_ACTIVE ("lock_button", p->lock_p);
2707 TOGGLE_ACTIVE ("verbose_button", p->verbose_p);
2708 TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p);
2709 TOGGLE_ACTIVE ("splash_button", p->splash_p);
2711 TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p);
2712 TOGGLE_ACTIVE ("grab_desk_button", p->grab_desktop_p);
2713 TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2714 TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2715 TOGGLE_ACTIVE ("install_button", p->install_cmap_p);
2716 TOGGLE_ACTIVE ("fade_button", p->fade_p);
2717 TOGGLE_ACTIVE ("unfade_button", p->unfade_p);
2721 case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio", True); break;
2722 case TEXT_FILE: TOGGLE_ACTIVE ("text_file_radio", True); break;
2723 case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
2724 case TEXT_URL: TOGGLE_ACTIVE ("text_url_radio", True); break;
2725 default: TOGGLE_ACTIVE ("text_host_radio", True); break;
2728 # undef TOGGLE_ACTIVE
2730 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2731 (p->image_directory ? p->image_directory : ""));
2732 gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2734 gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2737 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
2738 (p->text_literal ? p->text_literal : ""));
2739 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2740 (p->text_file ? p->text_file : ""));
2741 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2742 (p->text_program ? p->text_program : ""));
2743 gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
2744 (p->text_url ? p->text_url : ""));
2746 gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
2747 p->tmode == TEXT_LITERAL);
2748 gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
2749 p->tmode == TEXT_FILE);
2750 gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
2751 p->tmode == TEXT_FILE);
2752 gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
2753 p->tmode == TEXT_PROGRAM);
2754 gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
2755 p->tmode == TEXT_PROGRAM);
2756 gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
2757 p->tmode == TEXT_URL);
2760 /* Map the `saver_mode' enum to mode menu to values. */
2762 GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2765 for (i = 0; i < countof(mode_menu_order); i++)
2766 if (mode_menu_order[i] == p->mode)
2768 gtk_option_menu_set_history (opt, i);
2769 update_list_sensitivity (s);
2773 Bool found_any_writable_cells = False;
2774 Bool fading_possible = False;
2775 Bool dpms_supported = False;
2777 Display *dpy = GDK_DISPLAY();
2778 int nscreens = ScreenCount(dpy); /* real screens, not Xinerama */
2780 for (i = 0; i < nscreens; i++)
2782 Screen *s = ScreenOfDisplay (dpy, i);
2783 if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2785 found_any_writable_cells = True;
2790 fading_possible = found_any_writable_cells;
2791 #ifdef HAVE_XF86VMODE_GAMMA
2792 fading_possible = True;
2795 #ifdef HAVE_DPMS_EXTENSION
2797 int op = 0, event = 0, error = 0;
2798 if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2799 dpms_supported = True;
2801 #endif /* HAVE_DPMS_EXTENSION */
2804 # define SENSITIZE(NAME,SENSITIVEP) \
2805 gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2807 /* Blanking and Locking
2809 SENSITIZE ("lock_button", can_lock_p);
2810 SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
2811 SENSITIZE ("lock_mlabel", can_lock_p && p->lock_p);
2815 SENSITIZE ("dpms_frame", dpms_supported);
2816 SENSITIZE ("dpms_button", dpms_supported);
2817 SENSITIZE ("dpms_standby_label", dpms_supported && p->dpms_enabled_p);
2818 SENSITIZE ("dpms_standby_mlabel", dpms_supported && p->dpms_enabled_p);
2819 SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2820 SENSITIZE ("dpms_suspend_label", dpms_supported && p->dpms_enabled_p);
2821 SENSITIZE ("dpms_suspend_mlabel", dpms_supported && p->dpms_enabled_p);
2822 SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2823 SENSITIZE ("dpms_off_label", dpms_supported && p->dpms_enabled_p);
2824 SENSITIZE ("dpms_off_mlabel", dpms_supported && p->dpms_enabled_p);
2825 SENSITIZE ("dpms_off_spinbutton", dpms_supported && p->dpms_enabled_p);
2829 SENSITIZE ("cmap_frame", found_any_writable_cells || fading_possible);
2830 SENSITIZE ("install_button", found_any_writable_cells);
2831 SENSITIZE ("fade_button", fading_possible);
2832 SENSITIZE ("unfade_button", fading_possible);
2834 SENSITIZE ("fade_label", (fading_possible &&
2835 (p->fade_p || p->unfade_p)));
2836 SENSITIZE ("fade_spinbutton", (fading_possible &&
2837 (p->fade_p || p->unfade_p)));
2845 populate_popup_window (state *s)
2847 GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2848 char *doc_string = 0;
2850 /* #### not in Gtk 1.2
2851 gtk_label_set_selectable (doc);
2857 free_conf_data (s->cdata);
2862 saver_preferences *p = &s->prefs;
2863 int list_elt = selected_list_element (s);
2864 int hack_number = (list_elt >= 0 && list_elt < s->list_count
2865 ? s->list_elt_to_hack_number[list_elt]
2867 screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2870 GtkWidget *parent = name_to_widget (s, "settings_vbox");
2871 GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2872 const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2873 s->cdata = load_configurator (cmd_line, s->debug_p);
2874 if (s->cdata && s->cdata->widget)
2875 gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
2880 doc_string = (s->cdata
2881 ? s->cdata->description
2883 # else /* !HAVE_XML */
2884 doc_string = _("Descriptions not available: no XML support compiled in.");
2885 # endif /* !HAVE_XML */
2887 gtk_label_set_text (doc, (doc_string
2889 : _("No description available.")));
2894 sensitize_demo_widgets (state *s, Bool sensitive_p)
2896 const char *names[] = { "demo", "settings",
2897 "cmd_label", "cmd_text", "manual",
2898 "visual", "visual_combo" };
2900 for (i = 0; i < countof(names); i++)
2902 GtkWidget *w = name_to_widget (s, names[i]);
2903 gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2909 sensitize_menu_items (state *s, Bool force_p)
2911 static Bool running_p = False;
2912 static time_t last_checked = 0;
2913 time_t now = time ((time_t *) 0);
2914 const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
2918 if (force_p || now > last_checked + 10) /* check every 10 seconds */
2920 running_p = xscreensaver_running_p (s);
2921 last_checked = time ((time_t *) 0);
2924 for (i = 0; i < countof(names); i++)
2926 GtkWidget *w = name_to_widget (s, names[i]);
2927 gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
2932 /* When the File menu is de-posted after a "Restart Daemon" command,
2933 the window underneath doesn't repaint for some reason. I guess this
2934 is a bug in exposure handling in GTK or GDK. This works around it.
2937 force_dialog_repaint (state *s)
2940 /* Tell GDK to invalidate and repaint the whole window.
2942 GdkWindow *w = s->toplevel_widget->window;
2943 GdkRegion *region = gdk_region_new ();
2945 rect.x = rect.y = 0;
2946 rect.width = rect.height = 32767;
2947 gdk_region_union_with_rect (region, &rect);
2948 gdk_window_invalidate_region (w, region, True);
2949 gdk_region_destroy (region);
2950 gdk_window_process_updates (w, True);
2952 /* Force the server to send an exposure event by creating and then
2953 destroying a window as a child of the top level shell.
2955 Display *dpy = GDK_DISPLAY();
2956 Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
2958 XWindowAttributes xgwa;
2959 XGetWindowAttributes (dpy, parent, &xgwa);
2960 w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
2961 XMapRaised (dpy, w);
2962 XDestroyWindow (dpy, w);
2968 /* Even though we've given these text fields a maximum number of characters,
2969 their default size is still about 30 characters wide -- so measure out
2970 a string in their font, and resize them to just fit that.
2973 fix_text_entry_sizes (state *s)
2977 # if 0 /* appears no longer necessary with Gtk 1.2.10 */
2978 const char * const spinbuttons[] = {
2979 "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
2980 "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
2981 "dpms_off_spinbutton",
2982 "-fade_spinbutton" };
2986 for (i = 0; i < countof(spinbuttons); i++)
2988 const char *n = spinbuttons[i];
2990 while (*n == '-') n++, cols--;
2991 w = GTK_WIDGET (name_to_widget (s, n));
2992 width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
2993 gtk_widget_set_usize (w, width, -2);
2996 /* Now fix the width of the combo box.
2998 w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
2999 w = GTK_COMBO (w)->entry;
3000 width = gdk_string_width (w->style->font, "PseudoColor___");
3001 gtk_widget_set_usize (w, width, -2);
3003 /* Now fix the width of the file entry text.
3005 w = GTK_WIDGET (name_to_widget (s, "image_text"));
3006 width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
3007 gtk_widget_set_usize (w, width, -2);
3009 /* Now fix the width of the command line text.
3011 w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
3012 width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
3013 gtk_widget_set_usize (w, width, -2);
3017 /* Now fix the height of the list widget:
3018 make it default to being around 10 text-lines high instead of 4.
3020 w = GTK_WIDGET (name_to_widget (s, "list"));
3024 int leading = 3; /* approximate is ok... */
3028 PangoFontMetrics *pain =
3029 pango_context_get_metrics (gtk_widget_get_pango_context (w),
3030 w->style->font_desc,
3031 gtk_get_default_language ());
3032 height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
3033 pango_font_metrics_get_descent (pain));
3034 #else /* !HAVE_GTK2 */
3035 height = w->style->font->ascent + w->style->font->descent;
3036 #endif /* !HAVE_GTK2 */
3040 height += border * 2;
3041 w = GTK_WIDGET (name_to_widget (s, "scroller"));
3042 gtk_widget_set_usize (w, -2, height);
3049 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
3052 static char *up_arrow_xpm[] = {
3075 /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3076 the end of the array (Gtk 1.2.5.) */
3077 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3078 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3081 static char *down_arrow_xpm[] = {
3104 /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3105 the end of the array (Gtk 1.2.5.) */
3106 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3107 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3111 pixmapify_button (state *s, int down_p)
3115 GtkWidget *pixmapwid;
3119 w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
3120 style = gtk_widget_get_style (w);
3122 pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
3123 &style->bg[GTK_STATE_NORMAL],
3125 ? (gchar **) down_arrow_xpm
3126 : (gchar **) up_arrow_xpm));
3127 pixmapwid = gtk_pixmap_new (pixmap, mask);
3128 gtk_widget_show (pixmapwid);
3129 gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
3130 gtk_container_add (GTK_CONTAINER (w), pixmapwid);
3134 map_next_button_cb (GtkWidget *w, gpointer user_data)
3136 state *s = (state *) user_data;
3137 pixmapify_button (s, 1);
3141 map_prev_button_cb (GtkWidget *w, gpointer user_data)
3143 state *s = (state *) user_data;
3144 pixmapify_button (s, 0);
3146 #endif /* !HAVE_GTK2 */
3150 /* Work around a Gtk bug that causes label widgets to wrap text too early.
3154 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
3155 GtkAllocation *allocation,
3159 GtkWidgetAuxInfo *aux_info;
3161 aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
3163 aux_info->width = allocation->width;
3164 aux_info->height = -2;
3168 gtk_widget_size_request (label, &req);
3171 /* Feel the love. Thanks to Nat Friedman for finding this workaround.
3174 eschew_gtk_lossage (GtkLabel *label)
3176 GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
3177 aux_info->width = GTK_WIDGET (label)->allocation.width;
3178 aux_info->height = -2;
3182 gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
3184 gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
3185 GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
3188 gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
3190 gtk_widget_queue_resize (GTK_WIDGET (label));
3192 #endif /* !HAVE_GTK2 */
3196 populate_demo_window (state *s, int list_elt)
3198 saver_preferences *p = &s->prefs;
3201 GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
3202 GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "doc_frame"));
3203 GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
3204 GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
3205 GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list"));
3207 if (p->mode == BLANK_ONLY)
3210 pretty_name = strdup (_("Blank Screen"));
3211 schedule_preview (s, 0);
3213 else if (p->mode == DONT_BLANK)
3216 pretty_name = strdup (_("Screen Saver Disabled"));
3217 schedule_preview (s, 0);
3221 int hack_number = (list_elt >= 0 && list_elt < s->list_count
3222 ? s->list_elt_to_hack_number[list_elt]
3224 hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
3228 ? strdup (hack->name)
3229 : make_hack_name (hack->command))
3233 schedule_preview (s, hack->command);
3235 schedule_preview (s, 0);
3239 pretty_name = strdup (_("Preview"));
3241 gtk_frame_set_label (frame1, _(pretty_name));
3242 gtk_frame_set_label (frame2, _(pretty_name));
3244 gtk_entry_set_text (cmd, (hack ? hack->command : ""));
3245 gtk_entry_set_position (cmd, 0);
3249 sprintf (title, _("%s: %.100s Settings"),
3250 progclass, (pretty_name ? pretty_name : "???"));
3251 gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
3254 gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
3256 ? (hack->visual && *hack->visual
3261 sensitize_demo_widgets (s, (hack ? True : False));
3263 if (pretty_name) free (pretty_name);
3265 ensure_selected_item_visible (list);
3267 s->_selected_list_element = list_elt;
3272 widget_deleter (GtkWidget *widget, gpointer data)
3274 /* #### Well, I want to destroy these widgets, but if I do that, they get
3275 referenced again, and eventually I get a SEGV. So instead of
3276 destroying them, I'll just hide them, and leak a bunch of memory
3277 every time the disk file changes. Go go go Gtk!
3279 #### Ok, that's a lie, I get a crash even if I just hide the widget
3280 and don't ever delete it. Fuck!
3283 gtk_widget_destroy (widget);
3285 gtk_widget_hide (widget);
3290 static char **sort_hack_cmp_names_kludge;
3292 sort_hack_cmp (const void *a, const void *b)
3298 int aa = *(int *) a;
3299 int bb = *(int *) b;
3300 const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
3301 return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
3302 (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
3308 initialize_sort_map (state *s)
3310 saver_preferences *p = &s->prefs;
3313 if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
3314 if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
3315 if (s->hacks_available_p) free (s->hacks_available_p);
3317 s->list_elt_to_hack_number = (int *)
3318 calloc (sizeof(int), p->screenhacks_count + 1);
3319 s->hack_number_to_list_elt = (int *)
3320 calloc (sizeof(int), p->screenhacks_count + 1);
3321 s->hacks_available_p = (Bool *)
3322 calloc (sizeof(Bool), p->screenhacks_count + 1);
3323 s->total_available = 0;
3325 /* Check which hacks actually exist on $PATH
3327 for (i = 0; i < p->screenhacks_count; i++)
3329 screenhack *hack = p->screenhacks[i];
3330 int on = on_path_p (hack->command) ? 1 : 0;
3331 s->hacks_available_p[i] = on;
3332 s->total_available += on;
3335 /* Initialize list->hack table to unsorted mapping, omitting nonexistent
3339 for (i = 0; i < p->screenhacks_count; i++)
3341 if (!p->ignore_uninstalled_p ||
3342 s->hacks_available_p[i])
3343 s->list_elt_to_hack_number[j++] = i;
3347 for (; j < p->screenhacks_count; j++)
3348 s->list_elt_to_hack_number[j] = -1;
3351 /* Generate list of sortable names (once)
3353 sort_hack_cmp_names_kludge = (char **)
3354 calloc (sizeof(char *), p->screenhacks_count);
3355 for (i = 0; i < p->screenhacks_count; i++)
3357 screenhack *hack = p->screenhacks[i];
3358 char *name = (hack->name && *hack->name
3359 ? strdup (hack->name)
3360 : make_hack_name (hack->command));
3362 for (str = name; *str; str++)
3363 *str = tolower(*str);
3364 sort_hack_cmp_names_kludge[i] = name;
3367 /* Sort list->hack map alphabetically
3369 qsort (s->list_elt_to_hack_number,
3370 p->screenhacks_count,
3371 sizeof(*s->list_elt_to_hack_number),
3376 for (i = 0; i < p->screenhacks_count; i++)
3377 free (sort_hack_cmp_names_kludge[i]);
3378 free (sort_hack_cmp_names_kludge);
3379 sort_hack_cmp_names_kludge = 0;
3381 /* Build inverse table */
3382 for (i = 0; i < p->screenhacks_count; i++)
3384 int n = s->list_elt_to_hack_number[i];
3386 s->hack_number_to_list_elt[n] = i;
3392 maybe_reload_init_file (state *s)
3394 saver_preferences *p = &s->prefs;
3397 static Bool reentrant_lock = False;
3398 if (reentrant_lock) return 0;
3399 reentrant_lock = True;
3401 if (init_file_changed_p (p))
3403 const char *f = init_file_name();
3408 if (!f || !*f) return 0;
3409 b = (char *) malloc (strlen(f) + 1024);
3412 "file \"%s\" has changed, reloading.\n"),
3414 warning_dialog (s->toplevel_widget, b, False, 100);
3418 initialize_sort_map (s);
3420 list_elt = selected_list_element (s);
3421 list = name_to_widget (s, "list");
3422 gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
3423 populate_hack_list (s);
3424 force_list_select_item (s, list, list_elt, True);
3425 populate_prefs_page (s);
3426 populate_demo_window (s, list_elt);
3427 ensure_selected_item_visible (list);
3432 reentrant_lock = False;
3438 /* Making the preview window have the right X visual (so that GL works.)
3441 static Visual *get_best_gl_visual (state *);
3444 x_visual_to_gdk_visual (Visual *xv)
3446 GList *gvs = gdk_list_visuals();
3447 if (!xv) return gdk_visual_get_system();
3448 for (; gvs; gvs = gvs->next)
3450 GdkVisual *gv = (GdkVisual *) gvs->data;
3451 if (xv == GDK_VISUAL_XVISUAL (gv))
3454 fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
3455 blurb(), (unsigned long) xv->visualid);
3460 clear_preview_window (state *s)
3465 if (!s->toplevel_widget) return; /* very early */
3466 p = name_to_widget (s, "preview");
3469 if (!window) return;
3471 /* Flush the widget background down into the window, in case a subproc
3473 gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]);
3474 gdk_window_clear (window);
3477 int list_elt = selected_list_element (s);
3478 int hack_number = (list_elt >= 0
3479 ? s->list_elt_to_hack_number[list_elt]
3481 Bool available_p = (hack_number >= 0
3482 ? s->hacks_available_p [hack_number]
3484 Bool nothing_p = (s->total_available < 5);
3487 GtkWidget *notebook = name_to_widget (s, "preview_notebook");
3488 gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
3489 (s->running_preview_error_p
3490 ? (available_p ? 1 :
3493 #else /* !HAVE_GTK2 */
3494 if (s->running_preview_error_p)
3496 const char * const lines1[] = { N_("No Preview"), N_("Available") };
3497 const char * const lines2[] = { N_("Not"), N_("Installed") };
3498 int nlines = countof(lines1);
3499 int lh = p->style->font->ascent + p->style->font->descent;
3503 const char * const *lines = (available_p ? lines1 : lines2);
3505 gdk_window_get_size (window, &w, &h);
3506 y = (h - (lh * nlines)) / 2;
3507 y += p->style->font->ascent;
3508 for (i = 0; i < nlines; i++)
3510 int sw = gdk_string_width (p->style->font, _(lines[i]));
3511 int x = (w - sw) / 2;
3512 gdk_draw_string (window, p->style->font,
3513 p->style->fg_gc[GTK_STATE_NORMAL],
3518 #endif /* !HAVE_GTK2 */
3526 reset_preview_window (state *s)
3528 /* On some systems (most recently, MacOS X) OpenGL programs get confused
3529 when you kill one and re-start another on the same window. So maybe
3530 it's best to just always destroy and recreate the preview window
3531 when changing hacks, instead of always trying to reuse the same one?
3533 GtkWidget *pr = name_to_widget (s, "preview");
3534 if (GTK_WIDGET_REALIZED (pr))
3536 Window oid = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
3538 gtk_widget_hide (pr);
3539 gtk_widget_unrealize (pr);
3540 gtk_widget_realize (pr);
3541 gtk_widget_show (pr);
3542 id = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
3544 fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
3552 fix_preview_visual (state *s)
3554 GtkWidget *widget = name_to_widget (s, "preview");
3555 Visual *xvisual = get_best_gl_visual (s);
3556 GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
3557 GdkVisual *dvisual = gdk_visual_get_system();
3558 GdkColormap *cmap = (visual == dvisual
3559 ? gdk_colormap_get_system ()
3560 : gdk_colormap_new (visual, False));
3563 fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
3564 (visual == dvisual ? "default" : "non-default"),
3565 (xvisual ? (unsigned long) xvisual->visualid : 0L));
3567 if (!GTK_WIDGET_REALIZED (widget) ||
3568 gtk_widget_get_visual (widget) != visual)
3570 gtk_widget_unrealize (widget);
3571 gtk_widget_set_visual (widget, visual);
3572 gtk_widget_set_colormap (widget, cmap);
3573 gtk_widget_realize (widget);
3576 /* Set the Widget colors to be white-on-black. */
3578 GdkWindow *window = widget->window;
3579 GtkStyle *style = gtk_style_copy (widget->style);
3580 GdkColormap *cmap = gtk_widget_get_colormap (widget);
3581 GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
3582 GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
3583 GdkGC *fgc = gdk_gc_new(window);
3584 GdkGC *bgc = gdk_gc_new(window);
3585 if (!gdk_color_white (cmap, fg)) abort();
3586 if (!gdk_color_black (cmap, bg)) abort();
3587 gdk_gc_set_foreground (fgc, fg);
3588 gdk_gc_set_background (fgc, bg);
3589 gdk_gc_set_foreground (bgc, bg);
3590 gdk_gc_set_background (bgc, fg);
3591 style->fg_gc[GTK_STATE_NORMAL] = fgc;
3592 style->bg_gc[GTK_STATE_NORMAL] = fgc;
3593 gtk_widget_set_style (widget, style);
3595 /* For debugging purposes, put a title on the window (so that
3596 it can be easily found in the output of "xwininfo -tree".)
3598 gdk_window_set_title (window, "Preview");
3601 gtk_widget_show (widget);
3609 subproc_pretty_name (state *s)
3611 if (s->running_preview_cmd)
3613 char *ps = strdup (s->running_preview_cmd);
3614 char *ss = strchr (ps, ' ');
3616 ss = strrchr (ps, '/');
3627 return strdup ("???");
3632 reap_zombies (state *s)
3634 int wait_status = 0;
3636 while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
3640 if (pid == s->running_preview_pid)
3642 char *ss = subproc_pretty_name (s);
3643 fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
3644 (unsigned long) pid, ss);
3648 fprintf (stderr, "%s: pid %lu died\n", blurb(),
3649 (unsigned long) pid);
3655 /* Mostly lifted from driver/subprocs.c */
3657 get_best_gl_visual (state *s)
3659 Display *dpy = GDK_DISPLAY();
3668 av[ac++] = "xscreensaver-gl-helper";
3673 perror ("error creating pipe:");
3680 switch ((int) (forked = fork ()))
3684 sprintf (buf, "%s: couldn't fork", blurb());
3692 close (in); /* don't need this one */
3693 close (ConnectionNumber (dpy)); /* close display fd */
3695 if (dup2 (out, stdout_fd) < 0) /* pipe stdout */
3697 perror ("could not dup() a new stdout:");
3701 execvp (av[0], av); /* shouldn't return. */
3703 if (errno != ENOENT)
3705 /* Ignore "no such file or directory" errors, unless verbose.
3706 Issue all other exec errors, though. */
3707 sprintf (buf, "%s: running %s", blurb(), av[0]);
3711 /* Note that one must use _exit() instead of exit() in procs forked
3712 off of Gtk programs -- Gtk installs an atexit handler that has a
3713 copy of the X connection (which we've already closed, for safety.)
3714 If one uses exit() instead of _exit(), then one sometimes gets a
3715 spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3717 _exit (1); /* exits fork */
3723 int wait_status = 0;
3725 FILE *f = fdopen (in, "r");
3729 close (out); /* don't need this one */
3732 if (!fgets (buf, sizeof(buf)-1, f))
3736 /* Wait for the child to die. */
3737 waitpid (-1, &wait_status, 0);
3739 if (1 == sscanf (buf, "0x%x %c", &v, &c))
3745 fprintf (stderr, "%s: %s did not report a GL visual!\n",
3751 Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3753 fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3754 blurb(), av[0], result);
3766 kill_preview_subproc (state *s, Bool reset_p)
3768 s->running_preview_error_p = False;
3771 clear_preview_window (s);
3773 if (s->subproc_check_timer_id)
3775 gtk_timeout_remove (s->subproc_check_timer_id);
3776 s->subproc_check_timer_id = 0;
3777 s->subproc_check_countdown = 0;
3780 if (s->running_preview_pid)
3782 int status = kill (s->running_preview_pid, SIGTERM);
3783 char *ss = subproc_pretty_name (s);
3790 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3791 blurb(), (unsigned long) s->running_preview_pid, ss);
3796 sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3797 blurb(), (unsigned long) s->running_preview_pid, ss);
3801 else if (s->debug_p)
3802 fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3803 (unsigned long) s->running_preview_pid, ss);
3806 s->running_preview_pid = 0;
3807 if (s->running_preview_cmd) free (s->running_preview_cmd);
3808 s->running_preview_cmd = 0;
3815 reset_preview_window (s);
3816 clear_preview_window (s);
3821 /* Immediately and unconditionally launches the given process,
3822 after appending the -window-id option; sets running_preview_pid.
3825 launch_preview_subproc (state *s)
3827 saver_preferences *p = &s->prefs;
3831 const char *cmd = s->desired_preview_cmd;
3833 GtkWidget *pr = name_to_widget (s, "preview");
3836 reset_preview_window (s);
3838 window = pr->window;
3840 s->running_preview_error_p = False;
3842 if (s->preview_suppressed_p)
3844 kill_preview_subproc (s, False);
3848 new_cmd = malloc (strlen (cmd) + 40);
3850 id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3853 /* No window id? No command to run. */
3859 strcpy (new_cmd, cmd);
3860 sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
3864 kill_preview_subproc (s, False);
3867 s->running_preview_error_p = True;
3868 clear_preview_window (s);
3872 switch ((int) (forked = fork ()))
3877 sprintf (buf, "%s: couldn't fork", blurb());
3879 s->running_preview_error_p = True;
3885 close (ConnectionNumber (GDK_DISPLAY()));
3887 hack_subproc_environment (id, s->debug_p);
3889 usleep (250000); /* pause for 1/4th second before launching, to give
3890 the previous program time to die and flush its X
3891 buffer, so we don't get leftover turds on the
3894 exec_command (p->shell, new_cmd, p->nice_inferior);
3895 /* Don't bother printing an error message when we are unable to
3896 exec subprocesses; we handle that by polling the pid later.
3898 Note that one must use _exit() instead of exit() in procs forked
3899 off of Gtk programs -- Gtk installs an atexit handler that has a
3900 copy of the X connection (which we've already closed, for safety.)
3901 If one uses exit() instead of _exit(), then one sometimes gets a
3902 spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3904 _exit (1); /* exits child fork */
3909 if (s->running_preview_cmd) free (s->running_preview_cmd);
3910 s->running_preview_cmd = strdup (s->desired_preview_cmd);
3911 s->running_preview_pid = forked;
3915 char *ss = subproc_pretty_name (s);
3916 fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
3917 (unsigned long) forked, ss);
3924 schedule_preview_check (s);
3927 if (new_cmd) free (new_cmd);
3932 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
3935 hack_environment (state *s)
3937 static const char *def_path =
3938 # ifdef DEFAULT_PATH_PREFIX
3939 DEFAULT_PATH_PREFIX;
3944 Display *dpy = GDK_DISPLAY();
3945 const char *odpy = DisplayString (dpy);
3946 char *ndpy = (char *) malloc(strlen(odpy) + 20);
3947 strcpy (ndpy, "DISPLAY=");
3948 strcat (ndpy, odpy);
3953 fprintf (stderr, "%s: %s\n", blurb(), ndpy);
3955 /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
3956 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
3957 So we must leak it (and/or the previous setting). Yay.
3960 if (def_path && *def_path)
3962 const char *opath = getenv("PATH");
3963 char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
3964 strcpy (npath, "PATH=");
3965 strcat (npath, def_path);
3966 strcat (npath, ":");
3967 strcat (npath, opath);
3971 /* do not free(npath) -- see above */
3974 fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
3980 hack_subproc_environment (Window preview_window_id, Bool debug_p)
3982 /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
3983 necessary yet, but it will make programs work if we had invoked
3984 them with "-root" and not with "-window-id" -- which, of course,
3987 char *nssw = (char *) malloc (40);
3988 sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
3990 /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
3991 any more, right? It's not Posix, but everyone seems to have it. */
3996 fprintf (stderr, "%s: %s\n", blurb(), nssw);
3998 /* do not free(nssw) -- see above */
4002 /* Called from a timer:
4003 Launches the currently-chosen subprocess, if it's not already running.
4004 If there's a different process running, kills it.
4007 update_subproc_timer (gpointer data)
4009 state *s = (state *) data;
4010 if (! s->desired_preview_cmd)
4011 kill_preview_subproc (s, True);
4012 else if (!s->running_preview_cmd ||
4013 !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
4014 launch_preview_subproc (s);
4016 s->subproc_timer_id = 0;
4017 return FALSE; /* do not re-execute timer */
4021 /* Call this when you think you might want a preview process running.
4022 It will set a timer that will actually launch that program a second
4023 from now, if you haven't changed your mind (to avoid double-click
4024 spazzing, etc.) `cmd' may be null meaning "no process".
4027 schedule_preview (state *s, const char *cmd)
4029 int delay = 1000 * 0.5; /* 1/2 second hysteresis */
4034 fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
4036 fprintf (stderr, "%s: scheduling preview death\n", blurb());
4039 if (s->desired_preview_cmd) free (s->desired_preview_cmd);
4040 s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
4042 if (s->subproc_timer_id)
4043 gtk_timeout_remove (s->subproc_timer_id);
4044 s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
4048 /* Called from a timer:
4049 Checks to see if the subproc that should be running, actually is.
4052 check_subproc_timer (gpointer data)
4054 state *s = (state *) data;
4055 Bool again_p = True;
4057 if (s->running_preview_error_p || /* already dead */
4058 s->running_preview_pid <= 0)
4066 status = kill (s->running_preview_pid, 0);
4067 if (status < 0 && errno == ESRCH)
4068 s->running_preview_error_p = True;
4072 char *ss = subproc_pretty_name (s);
4073 fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
4074 (unsigned long) s->running_preview_pid, ss,
4075 (s->running_preview_error_p ? "dead" : "alive"));
4079 if (s->running_preview_error_p)
4081 clear_preview_window (s);
4086 /* Otherwise, it's currently alive. We might be checking again, or we
4087 might be satisfied. */
4089 if (--s->subproc_check_countdown <= 0)
4093 return TRUE; /* re-execute timer */
4096 s->subproc_check_timer_id = 0;
4097 s->subproc_check_countdown = 0;
4098 return FALSE; /* do not re-execute timer */
4103 /* Call this just after launching a subprocess.
4104 This sets a timer that will, five times a second for two seconds,
4105 check whether the program is still running. The assumption here
4106 is that if the process didn't stay up for more than a couple of
4107 seconds, then either the program doesn't exist, or it doesn't
4108 take a -window-id argument.
4111 schedule_preview_check (state *s)
4117 fprintf (stderr, "%s: scheduling check\n", blurb());
4119 if (s->subproc_check_timer_id)
4120 gtk_timeout_remove (s->subproc_check_timer_id);
4121 s->subproc_check_timer_id =
4122 gtk_timeout_add (1000 / ticks,
4123 check_subproc_timer, (gpointer) s);
4124 s->subproc_check_countdown = ticks * seconds;
4129 screen_blanked_p (void)
4133 unsigned long nitems, bytesafter;
4134 unsigned char *dataP = 0;
4135 Display *dpy = GDK_DISPLAY();
4136 Bool blanked_p = False;
4138 if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
4139 XA_SCREENSAVER_STATUS,
4140 0, 3, False, XA_INTEGER,
4141 &type, &format, &nitems, &bytesafter,
4144 && type == XA_INTEGER
4148 Atom *data = (Atom *) dataP;
4149 blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
4152 if (dataP) XFree (dataP);
4157 /* Wake up every now and then and see if the screen is blanked.
4158 If it is, kill off the small-window demo -- no point in wasting
4159 cycles by running two screensavers at once...
4162 check_blanked_timer (gpointer data)
4164 state *s = (state *) data;
4165 Bool blanked_p = screen_blanked_p ();
4166 if (blanked_p && s->running_preview_pid)
4169 fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
4170 kill_preview_subproc (s, True);
4173 return True; /* re-execute timer */
4177 /* How many screens are there (including Xinerama.)
4180 screen_count (Display *dpy)
4182 int nscreens = ScreenCount(dpy);
4183 # ifdef HAVE_XINERAMA
4186 int event_number, error_number;
4187 if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
4188 XineramaIsActive (dpy))
4190 XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
4191 if (xsi) XFree (xsi);
4194 # endif /* HAVE_XINERAMA */
4200 /* Setting window manager icon
4204 init_icon (GdkWindow *window)
4206 GdkBitmap *mask = 0;
4209 gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
4210 (gchar **) logo_50_xpm);
4212 gdk_window_set_icon (window, 0, pixmap, mask);
4216 /* The main demo-mode command loop.
4221 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
4222 XrmRepresentation *type, XrmValue *value, XPointer closure)
4225 for (i = 0; quarks[i]; i++)
4227 if (bindings[i] == XrmBindTightly)
4228 fprintf (stderr, (i == 0 ? "" : "."));
4229 else if (bindings[i] == XrmBindLoosely)
4230 fprintf (stderr, "*");
4232 fprintf (stderr, " ??? ");
4233 fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
4236 fprintf (stderr, ": %s\n", (char *) value->addr);
4244 the_network_is_not_the_computer (state *s)
4246 Display *dpy = GDK_DISPLAY();
4247 char *rversion = 0, *ruser = 0, *rhost = 0;
4248 char *luser, *lhost;
4250 struct passwd *p = getpwuid (getuid ());
4251 const char *d = DisplayString (dpy);
4253 # if defined(HAVE_UNAME)
4255 if (uname (&uts) < 0)
4256 lhost = "<UNKNOWN>";
4258 lhost = uts.nodename;
4260 strcpy (lhost, getenv("SYS$NODE"));
4261 # else /* !HAVE_UNAME && !VMS */
4262 strcat (lhost, "<UNKNOWN>");
4263 # endif /* !HAVE_UNAME && !VMS */
4265 if (p && p->pw_name)
4270 server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
4272 /* Make a buffer that's big enough for a number of copies of all the
4273 strings, plus some. */
4274 msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
4275 (ruser ? strlen(ruser) : 0) +
4276 (rhost ? strlen(rhost) : 0) +
4283 if (!rversion || !*rversion)
4287 "The XScreenSaver daemon doesn't seem to be running\n"
4288 "on display \"%s\". Launch it now?"),
4291 else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
4293 /* Warn that the two processes are running as different users.
4297 "%s is running as user \"%s\" on host \"%s\".\n"
4298 "But the xscreensaver managing display \"%s\"\n"
4299 "is running as user \"%s\" on host \"%s\".\n"
4301 "Since they are different users, they won't be reading/writing\n"
4302 "the same ~/.xscreensaver file, so %s isn't\n"
4303 "going to work right.\n"
4305 "You should either re-run %s as \"%s\", or re-run\n"
4306 "xscreensaver as \"%s\".\n"
4308 "Restart the xscreensaver daemon now?\n"),
4309 progname, luser, lhost,
4311 (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4313 progname, (ruser ? ruser : "???"),
4316 else if (rhost && *rhost && !!strcmp (rhost, lhost))
4318 /* Warn that the two processes are running on different hosts.
4322 "%s is running as user \"%s\" on host \"%s\".\n"
4323 "But the xscreensaver managing display \"%s\"\n"
4324 "is running as user \"%s\" on host \"%s\".\n"
4326 "If those two machines don't share a file system (that is,\n"
4327 "if they don't see the same ~%s/.xscreensaver file) then\n"
4328 "%s won't work right.\n"
4330 "Restart the daemon on \"%s\" as \"%s\" now?\n"),
4331 progname, luser, lhost,
4333 (ruser ? ruser : "???"), (rhost ? rhost : "???"),
4338 else if (!!strcmp (rversion, s->short_version))
4340 /* Warn that the version numbers don't match.
4344 "This is %s version %s.\n"
4345 "But the xscreensaver managing display \"%s\"\n"
4346 "is version %s. This could cause problems.\n"
4348 "Restart the xscreensaver daemon now?\n"),
4349 progname, s->short_version,
4356 warning_dialog (s->toplevel_widget, msg, True, 1);
4358 if (rversion) free (rversion);
4359 if (ruser) free (ruser);
4360 if (rhost) free (rhost);
4365 /* We use this error handler so that X errors are preceeded by the name
4366 of the program that generated them.
4369 demo_ehandler (Display *dpy, XErrorEvent *error)
4371 state *s = global_state_kludge; /* I hate C so much... */
4372 fprintf (stderr, "\nX error in %s:\n", blurb());
4373 XmuPrintDefaultErrorMessage (dpy, error, stderr);
4374 kill_preview_subproc (s, False);
4380 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
4381 of the program that generated them; and also that we can ignore one
4382 particular bogus error message that Gdk madly spews.
4385 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
4386 const gchar *message, gpointer user_data)
4388 /* Ignore the message "Got event for unknown window: 0x...".
4389 Apparently some events are coming in for the xscreensaver window
4390 (presumably reply events related to the ClientMessage) and Gdk
4391 feels the need to complain about them. So, just suppress any
4392 messages that look like that one.
4394 if (strstr (message, "unknown window"))
4397 fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
4398 (log_domain ? log_domain : progclass),
4399 (log_level == G_LOG_LEVEL_ERROR ? "error" :
4400 log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
4401 log_level == G_LOG_LEVEL_WARNING ? "warning" :
4402 log_level == G_LOG_LEVEL_MESSAGE ? "message" :
4403 log_level == G_LOG_LEVEL_INFO ? "info" :
4404 log_level == G_LOG_LEVEL_DEBUG ? "debug" : "???"),
4406 ((!*message || message[strlen(message)-1] != '\n')
4412 __extension__ /* shut up about "string length is greater than the length
4413 ISO C89 compilers are required to support" when including
4417 static char *defaults[] = {
4418 #include "XScreenSaver_ad.h"
4423 #ifdef HAVE_CRAPPLET
4424 static struct poptOption crapplet_options[] = {
4425 {NULL, '\0', 0, NULL, 0}
4427 #endif /* HAVE_CRAPPLET */
4430 const char *usage = "[--display dpy] [--prefs]"
4431 # ifdef HAVE_CRAPPLET
4434 "\n\t\t [--debug] [--sync] [--no-xshm] [--configdir dir]";
4437 map_popup_window_cb (GtkWidget *w, gpointer user_data)
4439 state *s = (state *) user_data;
4440 Boolean oi = s->initializing_p;
4442 GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
4444 s->initializing_p = True;
4446 eschew_gtk_lossage (label);
4448 s->initializing_p = oi;
4454 print_widget_tree (GtkWidget *w, int depth)
4457 for (i = 0; i < depth; i++)
4458 fprintf (stderr, " ");
4459 fprintf (stderr, "%s\n", gtk_widget_get_name (w));
4461 if (GTK_IS_LIST (w))
4463 for (i = 0; i < depth+1; i++)
4464 fprintf (stderr, " ");
4465 fprintf (stderr, "...list kids...\n");
4467 else if (GTK_IS_CONTAINER (w))
4469 GList *kids = gtk_container_children (GTK_CONTAINER (w));
4472 print_widget_tree (GTK_WIDGET (kids->data), depth+1);
4480 delayed_scroll_kludge (gpointer data)
4482 state *s = (state *) data;
4483 GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
4484 ensure_selected_item_visible (w);
4486 /* Oh, this is just fucking lovely, too. */
4487 w = GTK_WIDGET (name_to_widget (s, "preview"));
4488 gtk_widget_hide (w);
4489 gtk_widget_show (w);
4491 return FALSE; /* do not re-execute timer */
4497 create_xscreensaver_demo (void)
4501 nb = name_to_widget (global_state_kludge, "preview_notebook");
4502 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
4504 return name_to_widget (global_state_kludge, "xscreensaver_demo");
4508 create_xscreensaver_settings_dialog (void)
4512 box = name_to_widget (global_state_kludge, "dialog_action_area");
4514 w = name_to_widget (global_state_kludge, "adv_button");
4515 gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4517 w = name_to_widget (global_state_kludge, "std_button");
4518 gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
4520 return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
4523 #endif /* HAVE_GTK2 */
4526 main (int argc, char **argv)
4530 saver_preferences *p;
4534 Widget toplevel_shell;
4535 char *real_progname = argv[0];
4538 Bool crapplet_p = False;
4542 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
4543 textdomain (GETTEXT_PACKAGE);
4546 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
4547 # else /* !HAVE_GTK2 */
4548 if (!setlocale (LC_ALL, ""))
4549 fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
4550 # endif /* !HAVE_GTK2 */
4552 #endif /* ENABLE_NLS */
4554 str = strrchr (real_progname, '/');
4555 if (str) real_progname = str+1;
4558 memset (s, 0, sizeof(*s));
4559 s->initializing_p = True;
4562 global_state_kludge = s; /* I hate C so much... */
4564 progname = real_progname;
4566 s->short_version = (char *) malloc (5);
4567 memcpy (s->short_version, screensaver_id + 17, 4);
4568 s->short_version [4] = 0;
4571 /* Register our error message logger for every ``log domain'' known.
4572 There's no way to do this globally, so I grepped the Gtk/Gdk sources
4573 for all of the domains that seem to be in use.
4576 const char * const domains[] = { 0,
4577 "Gtk", "Gdk", "GLib", "GModule",
4578 "GThread", "Gnome", "GnomeUI" };
4579 for (i = 0; i < countof(domains); i++)
4580 g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
4583 #ifdef DEFAULT_ICONDIR /* from -D on compile line */
4586 const char *dir = DEFAULT_ICONDIR;
4587 if (*dir) add_pixmap_directory (dir);
4589 # endif /* !HAVE_GTK2 */
4590 #endif /* DEFAULT_ICONDIR */
4592 /* This is gross, but Gtk understands --display and not -display...
4594 for (i = 1; i < argc; i++)
4595 if (argv[i][0] && argv[i][1] &&
4596 !strncmp(argv[i], "-display", strlen(argv[i])))
4597 argv[i] = "--display";
4600 /* We need to parse this arg really early... Sigh. */
4601 for (i = 1; i < argc; i++)
4604 (!strcmp(argv[i], "--crapplet") ||
4605 !strcmp(argv[i], "--capplet")))
4607 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
4610 for (j = i; j < argc; j++) /* remove it from the list */
4611 argv[j] = argv[j+1];
4613 # else /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4614 fprintf (stderr, "%s: not compiled with --crapplet support\n",
4616 fprintf (stderr, "%s: %s\n", real_progname, usage);
4618 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4621 (!strcmp(argv[i], "--debug") ||
4622 !strcmp(argv[i], "-debug") ||
4623 !strcmp(argv[i], "-d")))
4627 for (j = i; j < argc; j++) /* remove it from the list */
4628 argv[j] = argv[j+1];
4635 (!strcmp(argv[i], "-geometry") ||
4636 !strcmp(argv[i], "-geom") ||
4637 !strcmp(argv[i], "-geo") ||
4638 !strcmp(argv[i], "-g")))
4642 for (j = i; j < argc; j++) /* remove them from the list */
4643 argv[j] = argv[j+2];
4650 (!strcmp(argv[i], "--configdir")))
4654 hack_configuration_path = argv[i+1];
4655 for (j = i; j < argc; j++) /* remove them from the list */
4656 argv[j] = argv[j+2];
4660 if (0 != stat (hack_configuration_path, &st))
4663 sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
4667 else if (!S_ISDIR (st.st_mode))
4669 fprintf (stderr, "%s: not a directory: %s\n",
4670 blurb(), hack_configuration_path);
4678 fprintf (stderr, "%s: using config directory \"%s\"\n",
4679 progname, hack_configuration_path);
4682 /* Let Gtk open the X connection, then initialize Xt to use that
4683 same connection. Doctor Frankenstein would be proud.
4685 # ifdef HAVE_CRAPPLET
4688 GnomeClient *client;
4689 GnomeClientFlags flags = 0;
4691 int init_results = gnome_capplet_init ("screensaver-properties",
4693 argc, argv, NULL, 0, NULL);
4695 0 upon successful initialization;
4696 1 if --init-session-settings was passed on the cmdline;
4697 2 if --ignore was passed on the cmdline;
4700 So the 1 signifies just to init the settings, and quit, basically.
4701 (Meaning launch the xscreensaver daemon.)
4704 if (init_results < 0)
4707 g_error ("An initialization error occurred while "
4708 "starting xscreensaver-capplet.\n");
4710 fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
4711 real_progname, init_results);
4716 client = gnome_master_client ();
4719 flags = gnome_client_get_flags (client);
4721 if (flags & GNOME_CLIENT_IS_CONNECTED)
4724 gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
4725 gnome_client_get_id (client));
4728 char *session_args[20];
4730 session_args[i++] = real_progname;
4731 session_args[i++] = "--capplet";
4732 session_args[i++] = "--init-session-settings";
4733 session_args[i] = 0;
4734 gnome_client_set_priority (client, 20);
4735 gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
4736 gnome_client_set_restart_command (client, i, session_args);
4740 gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
4743 gnome_client_flush (client);
4746 if (init_results == 1)
4748 system ("xscreensaver -nosplash &");
4754 # endif /* HAVE_CRAPPLET */
4756 gtk_init (&argc, &argv);
4760 /* We must read exactly the same resources as xscreensaver.
4761 That means we must have both the same progclass *and* progname,
4762 at least as far as the resource database is concerned. So,
4763 put "xscreensaver" in argv[0] while initializing Xt.
4765 argv[0] = "xscreensaver";
4769 /* Teach Xt to use the Display that Gtk/Gdk have already opened.
4771 XtToolkitInitialize ();
4772 app = XtCreateApplicationContext ();
4773 dpy = GDK_DISPLAY();
4774 XtAppSetFallbackResources (app, defaults);
4775 XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
4776 toplevel_shell = XtAppCreateShell (progname, progclass,
4777 applicationShellWidgetClass,
4780 dpy = XtDisplay (toplevel_shell);
4781 db = XtDatabase (dpy);
4782 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
4783 XSetErrorHandler (demo_ehandler);
4785 /* Let's just ignore these. They seem to confuse Irix Gtk... */
4786 signal (SIGPIPE, SIG_IGN);
4788 /* After doing Xt-style command-line processing, complain about any
4789 unrecognized command-line arguments.
4791 for (i = 1; i < argc; i++)
4793 char *str = argv[i];
4794 if (str[0] == '-' && str[1] == '-')
4796 if (!strcmp (str, "-prefs"))
4798 else if (crapplet_p)
4799 /* There are lots of random args that we don't care about when we're
4800 started as a crapplet, so just ignore unknown args in that case. */
4804 fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
4806 fprintf (stderr, "%s: %s\n", real_progname, usage);
4811 /* Load the init file, which may end up consulting the X resource database
4812 and the site-wide app-defaults file. Note that at this point, it's
4813 important that `progname' be "xscreensaver", rather than whatever
4817 s->nscreens = screen_count (dpy);
4819 hack_environment (s); /* must be before initialize_sort_map() */
4822 initialize_sort_map (s);
4824 /* Now that Xt has been initialized, and the resources have been read,
4825 we can set our `progname' variable to something more in line with
4828 progname = real_progname;
4832 /* Print out all the resources we read. */
4834 XrmName name = { 0 };
4835 XrmClass class = { 0 };
4837 XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
4843 /* Intern the atoms that xscreensaver_command() needs.
4845 XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
4846 XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
4847 XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
4848 XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
4849 XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
4850 XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
4851 XA_SELECT = XInternAtom (dpy, "SELECT", False);
4852 XA_DEMO = XInternAtom (dpy, "DEMO", False);
4853 XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
4854 XA_BLANK = XInternAtom (dpy, "BLANK", False);
4855 XA_LOCK = XInternAtom (dpy, "LOCK", False);
4856 XA_EXIT = XInternAtom (dpy, "EXIT", False);
4857 XA_RESTART = XInternAtom (dpy, "RESTART", False);
4860 /* Create the window and all its widgets.
4862 s->base_widget = create_xscreensaver_demo ();
4863 s->popup_widget = create_xscreensaver_settings_dialog ();
4864 s->toplevel_widget = s->base_widget;
4867 /* Set the main window's title. */
4869 char *base_title = _("Screensaver Preferences");
4870 char *v = (char *) strdup(strchr(screensaver_id, ' '));
4871 char *s1, *s2, *s3, *s4;
4872 s1 = (char *) strchr(v, ' '); s1++;
4873 s2 = (char *) strchr(s1, ' ');
4874 s3 = (char *) strchr(v, '('); s3++;
4875 s4 = (char *) strchr(s3, ')');
4879 window_title = (char *) malloc (strlen (base_title) +
4880 strlen (progclass) +
4881 strlen (s1) + strlen (s3) +
4883 sprintf (window_title, "%s (%s %s, %s)", base_title, progclass, s1, s3);
4884 gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
4885 gtk_window_set_title (GTK_WINDOW (s->popup_widget), window_title);
4889 /* Adjust the (invisible) notebooks on the popup dialog... */
4891 GtkNotebook *notebook =
4892 GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
4893 GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
4897 gtk_widget_hide (std);
4898 # else /* !HAVE_XML */
4899 /* Make the advanced page be the only one available. */
4900 gtk_widget_set_sensitive (std, False);
4901 std = GTK_WIDGET (name_to_widget (s, "adv_button"));
4902 gtk_widget_hide (std);
4904 # endif /* !HAVE_XML */
4906 gtk_notebook_set_page (notebook, page);
4907 gtk_notebook_set_show_tabs (notebook, False);
4910 /* Various other widget initializations...
4912 gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
4913 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4915 gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
4916 GTK_SIGNAL_FUNC (wm_popup_close_cb),
4919 populate_hack_list (s);
4920 populate_prefs_page (s);
4921 sensitize_demo_widgets (s, False);
4922 fix_text_entry_sizes (s);
4923 scroll_to_current_hack (s);
4925 gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
4926 "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
4930 gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
4931 "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
4933 gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
4934 "map", GTK_SIGNAL_FUNC(map_next_button_cb),
4936 #endif /* !HAVE_GTK2 */
4938 /* Hook up callbacks to the items on the mode menu. */
4940 GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
4941 GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
4942 GList *kids = gtk_container_children (GTK_CONTAINER (menu));
4944 for (i = 0; kids; kids = kids->next, i++)
4946 gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
4947 GTK_SIGNAL_FUNC (mode_menu_item_cb),
4950 /* The "random-same" mode menu item does not appear unless
4951 there are multple screens.
4953 if (s->nscreens <= 1 &&
4954 mode_menu_order[i] == RANDOM_HACKS_SAME)
4955 gtk_widget_hide (GTK_WIDGET (kids->data));
4958 if (s->nscreens <= 1) /* recompute option-menu size */
4960 gtk_widget_unrealize (GTK_WIDGET (menu));
4961 gtk_widget_realize (GTK_WIDGET (menu));
4966 /* Handle the -prefs command-line argument. */
4969 GtkNotebook *notebook =
4970 GTK_NOTEBOOK (name_to_widget (s, "notebook"));
4971 gtk_notebook_set_page (notebook, 1);
4974 # ifdef HAVE_CRAPPLET
4978 GtkWidget *outer_vbox;
4980 gtk_widget_hide (s->toplevel_widget);
4982 capplet = capplet_widget_new ();
4984 /* Make there be a "Close" button instead of "OK" and "Cancel" */
4985 # ifdef HAVE_CRAPPLET_IMMEDIATE
4986 capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
4987 # endif /* HAVE_CRAPPLET_IMMEDIATE */
4988 /* In crapplet-mode, take off the menubar. */
4989 gtk_widget_hide (name_to_widget (s, "menubar"));
4991 /* Reparent our top-level container to be a child of the capplet
4994 outer_vbox = GTK_BIN (s->toplevel_widget)->child;
4995 gtk_widget_ref (outer_vbox);
4996 gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
4998 STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
4999 gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
5001 /* Find the window above us, and set the title and close handler. */
5003 GtkWidget *window = capplet;
5004 while (window && !GTK_IS_WINDOW (window))
5005 window = window->parent;
5008 gtk_window_set_title (GTK_WINDOW (window), window_title);
5009 gtk_signal_connect (GTK_OBJECT (window), "delete_event",
5010 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
5015 s->toplevel_widget = capplet;
5017 # endif /* HAVE_CRAPPLET */
5020 /* The Gnome folks hate the menubar. I think it's important to have access
5021 to the commands on the File menu (Restart Daemon, etc.) and to the
5022 About and Documentation commands on the Help menu.
5026 gtk_widget_hide (name_to_widget (s, "menubar"));
5030 free (window_title);
5034 /* After picking the default size, allow -geometry to override it. */
5036 gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
5039 gtk_widget_show (s->toplevel_widget);
5040 init_icon (GTK_WIDGET (s->toplevel_widget)->window); /* after `show' */
5041 fix_preview_visual (s);
5043 /* Realize page zero, so that we can diddle the scrollbar when the
5044 user tabs back to it -- otherwise, the current hack isn't scrolled
5045 to the first time they tab back there, when started with "-prefs".
5046 (Though it is if they then tab away, and back again.)
5048 #### Bah! This doesn't work. Gtk eats my ass! Someone who
5049 #### understands this crap, explain to me how to make this work.
5051 gtk_widget_realize (name_to_widget (s, "demos_table"));
5054 gtk_timeout_add (60 * 1000, check_blanked_timer, s);
5057 /* Issue any warnings about the running xscreensaver daemon. */
5058 the_network_is_not_the_computer (s);
5061 /* Run the Gtk event loop, and not the Xt event loop. This means that
5062 if there were Xt timers or fds registered, they would never get serviced,
5063 and if there were any Xt widgets, they would never have events delivered.
5064 Fortunately, we're using Gtk for all of the UI, and only initialized
5065 Xt so that we could process the command line and use the X resource
5068 s->initializing_p = False;
5070 /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
5071 after we start up. Otherwise, it always appears scrolled to the top
5072 when in crapplet-mode. */
5073 gtk_timeout_add (500, delayed_scroll_kludge, s);
5077 /* Load every configurator in turn, to scan them for errors all at once. */
5080 for (i = 0; i < p->screenhacks_count; i++)
5082 screenhack *hack = p->screenhacks[i];
5083 conf_data *d = load_configurator (hack->command, False);
5084 if (d) free_conf_data (d);
5090 # ifdef HAVE_CRAPPLET
5092 capplet_gtk_main ();
5094 # endif /* HAVE_CRAPPLET */
5097 kill_preview_subproc (s, False);
5101 #endif /* HAVE_GTK -- whole file */