X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fdemo-Gtk.c;h=298860a7e130a07dfb0cd4f4c0a9cd5435e8b27c;hp=f0e88246f66b09fe60f2a6e79562428c243b22cb;hb=ffd8c0873576a9e3065696a624dce6b766b77062;hpb=72c1f4c1dc6ab07fe121a327ff1c30bf51ef74c1 diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c index f0e88246..298860a7 100644 --- a/driver/demo-Gtk.c +++ b/driver/demo-Gtk.c @@ -1,5 +1,5 @@ /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs. - * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski + * xscreensaver, Copyright (c) 1993-2004 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,12 +16,25 @@ #ifdef HAVE_GTK /* whole file */ +#include + #include #ifdef HAVE_UNISTD_H # include #endif +# ifdef __GNUC__ +# define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */ +# else +# define STFU /* */ +# endif + + +#ifdef ENABLE_NLS +# include +#endif /* ENABLE_NLS */ + #ifndef VMS # include /* for getpwuid() */ #else /* VMS */ @@ -33,6 +46,17 @@ #endif /* HAVE_UNAME */ #include +#include +#include +#include + + +#include +#include +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif + #include /* for CARD32 */ #include /* for XA_INTEGER */ @@ -54,11 +78,33 @@ # include "xmu.h" #endif +#include +#ifdef HAVE_CRAPPLET +# include +# include +#endif + +#include + +#ifdef HAVE_GTK2 +#include +#endif /* HAVE_GTK2 */ + +#if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR) +# define GLADE_DIR DEFAULT_ICONDIR +#endif +#if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR) +# define DEFAULT_ICONDIR GLADE_DIR +#endif + +#ifndef HAVE_XML + /* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML. + It is unused otherwise, so in that case, stub it out. */ + static const char *hack_configuration_path = 0; +#endif -#include -extern Display *gdk_display; #include "version.h" #include "prefs.h" @@ -67,12 +113,33 @@ extern Display *gdk_display; #include "remote.h" /* for xscreensaver_command() */ #include "usleep.h" +#include "logo-50.xpm" +#include "logo-180.xpm" + +#undef dgettext /* else these are defined twice... */ +#undef dcgettext + #include "demo-Gtk-widgets.h" +#include "demo-Gtk-support.h" +#include "demo-Gtk-conf.h" #include #include #include +#ifdef HAVE_GTK2 +enum { + COL_ENABLED, + COL_NAME, + COL_LAST +}; +#endif /* HAVE_GTK2 */ + +/* from exec.c */ +extern void exec_command (const char *shell, const char *command, int nice); + +static void hack_subproc_environment (Window preview_window_id, Bool debug_p); + #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) @@ -81,19 +148,56 @@ char *progname = 0; char *progclass = "XScreenSaver"; XrmDatabase db; +/* The order of the items in the mode menu. */ +static int mode_menu_order[] = { + DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS }; + + typedef struct { - saver_preferences *a, *b; -} prefs_pair; -static void *global_prefs_pair; /* I hate C so much... */ + char *short_version; /* version number of this xscreensaver build */ + + GtkWidget *toplevel_widget; /* the main window */ + GtkWidget *base_widget; /* root of our hierarchy (for name lookups) */ + GtkWidget *popup_widget; /* the "Settings" dialog */ + conf_data *cdata; /* private data for per-hack configuration */ + +#ifdef HAVE_GTK2 + GladeXML *glade_ui; /* Glade UI file */ +#endif /* HAVE_GTK2 */ + + Bool debug_p; /* whether to print diagnostics */ + Bool initializing_p; /* flag for breaking recursion loops */ + Bool saving_p; /* flag for breaking recursion loops */ + + char *desired_preview_cmd; /* subprocess we intend to run */ + char *running_preview_cmd; /* subprocess we are currently running */ + pid_t running_preview_pid; /* pid of forked subproc (might be dead) */ + Bool running_preview_error_p; /* whether the pid died abnormally */ + + Bool preview_suppressed_p; /* flag meaning "don't launch subproc" */ + int subproc_timer_id; /* timer to delay subproc launch */ + int subproc_check_timer_id; /* timer to check whether it started up */ + int subproc_check_countdown; /* how many more checks left */ + int *list_elt_to_hack_number; /* table for sorting the hack list */ + int *hack_number_to_list_elt; /* the inverse table */ + Bool *hacks_available_p; /* whether hacks are on $PATH */ + int list_count; /* how many items are in the list: this may be + less than p->screenhacks_count, if some are + suppressed. */ + int _selected_list_element; /* don't use this: call + selected_list_element() instead */ -char *blurb (void) { return progname; } + saver_preferences prefs; -static void run_hack (int which); +} state; -static char *short_version = 0; + +/* Total fucking evilness due to the fact that it's rocket science to get + a closure object of our own down into the various widget callbacks. */ +static state *global_state_kludge; Atom XA_VROOT; Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION; @@ -101,32 +205,97 @@ Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO; Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT; +static void populate_demo_window (state *, int list_elt); +static void populate_prefs_page (state *); +static void populate_popup_window (state *); + +static Bool flush_dialog_changes_and_save (state *); +static Bool flush_popup_changes_and_save (state *); -static void populate_demo_window (GtkWidget *toplevel, - int which, prefs_pair *pair); -static void populate_prefs_page (GtkWidget *top, prefs_pair *pair); +static int maybe_reload_init_file (state *); +static void await_xscreensaver (state *); + +static void schedule_preview (state *, const char *cmd); +static void kill_preview_subproc (state *, Bool reset_p); +static void schedule_preview_check (state *); /* Some random utility functions */ +const char * +blurb (void) +{ + time_t now = time ((time_t *) 0); + char *ct = (char *) ctime (&now); + static char buf[255]; + int n = strlen(progname); + if (n > 100) n = 99; + strncpy(buf, progname, n); + buf[n++] = ':'; + buf[n++] = ' '; + strncpy(buf+n, ct+11, 8); + strcpy(buf+n+9, ": "); + return buf; +} + + static GtkWidget * -name_to_widget (GtkWidget *widget, const char *name) +name_to_widget (state *s, const char *name) { - while (1) + GtkWidget *w; + if (!s) abort(); + if (!name) abort(); + if (!*name) abort(); + +#ifdef HAVE_GTK2 + if (!s->glade_ui) { - GtkWidget *parent = (GTK_IS_MENU (widget) - ? gtk_menu_get_attach_widget (GTK_MENU (widget)) - : widget->parent); - if (parent) - widget = parent; - else - break; + /* First try to load the Glade file from the current directory; + if there isn't one there, check the installed directory. + */ +# define GLADE_FILE_NAME "xscreensaver-demo.glade2" + const char * const files[] = { GLADE_FILE_NAME, + GLADE_DIR "/" GLADE_FILE_NAME }; + int i; + for (i = 0; i < countof (files); i++) + { + struct stat st; + if (!stat (files[i], &st)) + { + s->glade_ui = glade_xml_new (files[i], NULL, NULL); + break; + } + } + if (!s->glade_ui) + { + fprintf (stderr, + "%s: could not load \"" GLADE_FILE_NAME "\"\n" + "\tfrom " GLADE_DIR "/ or current directory.\n", + blurb()); + exit (-1); + } +# undef GLADE_FILE_NAME + + glade_xml_signal_autoconnect (s->glade_ui); } - return (GtkWidget *) gtk_object_get_data (GTK_OBJECT (widget), name); -} + w = glade_xml_get_widget (s->glade_ui, name); + +#else /* !HAVE_GTK2 */ + + w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget), + name); + if (w) return w; + w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget), + name); +#endif /* HAVE_GTK2 */ + if (w) return w; + + fprintf (stderr, "%s: no widget \"%s\"\n", blurb(), name); + abort(); +} /* Why this behavior isn't automatic in *either* toolkit, I'll never know. @@ -135,6 +304,24 @@ name_to_widget (GtkWidget *widget, const char *name) static void ensure_selected_item_visible (GtkWidget *widget) { +#ifdef HAVE_GTK2 + GtkTreePath *path; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + path = gtk_tree_path_new_first (); + else + path = gtk_tree_model_get_path (model, &iter); + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE); + + gtk_tree_path_free (path); + +#else /* !HAVE_GTK2 */ + GtkScrolledWindow *scroller = 0; GtkViewport *vp = 0; GtkList *list_widget = 0; @@ -142,7 +329,7 @@ ensure_selected_item_visible (GtkWidget *widget) GList *kids; int nkids = 0; GtkWidget *selected = 0; - int which = -1; + int list_elt = -1; GtkAdjustment *adj; gint parent_h, child_y, child_h, children_h, ignore; double ratio_t, ratio_b; @@ -173,13 +360,13 @@ ensure_selected_item_visible (GtkWidget *widget) if (!selected) return; - which = gtk_list_child_position (list_widget, GTK_WIDGET (selected)); + list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected)); for (kids = gtk_container_children (GTK_CONTAINER (list_widget)); kids; kids = kids->next) nkids++; - adj = gtk_scrolled_window_get_vadjustment (scroller); + adj = gtk_scrolled_window_get_vadjustment (scroller); gdk_window_get_geometry (GTK_WIDGET(vp)->window, &ignore, &ignore, &ignore, &parent_h, &ignore); @@ -190,6 +377,9 @@ ensure_selected_item_visible (GtkWidget *widget) ratio_t = ((double) child_y) / ((double) children_h); ratio_b = ((double) child_y + child_h) / ((double) children_h); + if (adj->upper == 0.0) /* no items in list */ + return; + if (ratio_t < (adj->value / adj->upper) || ratio_b > ((adj->value + adj->page_size) / adj->upper)) { @@ -216,11 +406,11 @@ ensure_selected_item_visible (GtkWidget *widget) gtk_adjustment_set_value (adj, target); } +#endif /* !HAVE_GTK2 */ } - static void -warning_dialog_dismiss_cb (GtkButton *button, gpointer user_data) +warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data) { GtkWidget *shell = GTK_WIDGET (user_data); while (shell->parent) @@ -229,8 +419,17 @@ warning_dialog_dismiss_cb (GtkButton *button, gpointer user_data) } +void restart_menu_cb (GtkWidget *widget, gpointer user_data); + +static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data) +{ + restart_menu_cb (widget, user_data); + warning_dialog_dismiss_cb (widget, user_data); +} + static void -warning_dialog (GtkWidget *parent, const char *message, int center) +warning_dialog (GtkWidget *parent, const char *message, + Boolean restart_button_p, int center) { char *msg = strdup (message); char *head; @@ -238,11 +437,19 @@ warning_dialog (GtkWidget *parent, const char *message, int center) GtkWidget *dialog = gtk_dialog_new (); GtkWidget *label = 0; GtkWidget *ok = 0; + GtkWidget *cancel = 0; int i = 0; - while (parent->parent) + while (parent && !parent->window) parent = parent->parent; + if (!parent || + !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */ + { + fprintf (stderr, "%s: too early for dialog?\n", progname); + return; + } + head = msg; while (head) { @@ -253,12 +460,23 @@ warning_dialog (GtkWidget *parent, const char *message, int center) sprintf (name, "label%d", i++); { - char buf[255]; label = gtk_label_new (head); - sprintf (buf, "warning_dialog.%s.font", name); - GTK_WIDGET (label)->style = gtk_style_copy (GTK_WIDGET (label)->style); - GTK_WIDGET (label)->style->font = - gdk_font_load (get_string_resource (buf, "Dialog.Label.Font")); +#ifdef HAVE_GTK2 + gtk_label_set_selectable (GTK_LABEL (label), TRUE); +#endif /* HAVE_GTK2 */ + +#ifndef HAVE_GTK2 + if (i == 1) + { + GTK_WIDGET (label)->style = + gtk_style_copy (GTK_WIDGET (label)->style); + GTK_WIDGET (label)->style->font = + gdk_font_load (get_string_resource("warning_dialog.headingFont", + "Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label), + GTK_WIDGET (label)->style); + } +#endif /* !HAVE_GTK2 */ if (center <= 0) gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), @@ -283,132 +501,283 @@ warning_dialog (GtkWidget *parent, const char *message, int center) gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), label, TRUE, TRUE, 0); - ok = gtk_button_new_with_label ( - get_string_resource ("warning_dialog.ok.label", - "warning_dialog.Button.Label")); +#ifdef HAVE_GTK2 + if (restart_button_p) + { + cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_container_add (GTK_CONTAINER (label), cancel); + } + + ok = gtk_button_new_from_stock (GTK_STOCK_OK); gtk_container_add (GTK_CONTAINER (label), ok); +#else /* !HAVE_GTK2 */ + + ok = gtk_button_new_with_label ("OK"); + gtk_container_add (GTK_CONTAINER (label), ok); + + if (restart_button_p) + { + cancel = gtk_button_new_with_label ("Cancel"); + gtk_container_add (GTK_CONTAINER (label), cancel); + } + +#endif /* !HAVE_GTK2 */ + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); gtk_window_set_title (GTK_WINDOW (dialog), progclass); + STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT); gtk_widget_show (ok); + gtk_widget_grab_focus (ok); + + if (cancel) + { + STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); + gtk_widget_show (cancel); + } gtk_widget_show (label); gtk_widget_show (dialog); -/* gtk_window_set_default (GTK_WINDOW (dialog), ok);*/ - gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", - GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), - (gpointer) dialog); + if (restart_button_p) + { + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_restart_cb), + (gpointer) dialog); + gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + } + else + { + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + } + gdk_window_set_transient_for (GTK_WIDGET (dialog)->window, GTK_WIDGET (parent)->window); +#ifdef HAVE_GTK2 + gtk_window_present (GTK_WINDOW (dialog)); +#else /* !HAVE_GTK2 */ gdk_window_show (GTK_WIDGET (dialog)->window); gdk_window_raise (GTK_WIDGET (dialog)->window); +#endif /* !HAVE_GTK2 */ free (msg); } static void -run_hack (int which) +run_cmd (state *s, Atom command, int arg) { + char *err = 0; int status; - if (which < 0) return; - status = xscreensaver_command (gdk_display, XA_DEMO, which + 1, False); + + flush_dialog_changes_and_save (s); + status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err); + + /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */ + if (status < 0 && err && strstr (err, "unexpectedly deleted")) + status = 0; + if (status < 0) { char buf [255]; - sprintf (buf, - "Error:\n\n" - "The DEMO %d command failed (%d).\n", which + 1, status); -#if 0 - warning_dialog (GTK_WIDGET (menuitem), buf, 1); -#endif + if (err) + sprintf (buf, "Error:\n\n%s", err); + else + strcpy (buf, "Unknown error!"); + warning_dialog (s->toplevel_widget, buf, False, 100); } + if (err) free (err); } - -/* Button callbacks - */ - -void -exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data) -{ - gtk_main_quit (); -} - static void -wm_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) +run_hack (state *s, int list_elt, Bool report_errors_p) { - exit_menu_cb (NULL, NULL); + int hack_number; + if (list_elt < 0) return; + hack_number = s->list_elt_to_hack_number[list_elt]; + + flush_dialog_changes_and_save (s); + schedule_preview (s, 0); + if (report_errors_p) + run_cmd (s, XA_DEMO, hack_number + 1); + else + { + char *s = 0; + xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1, + False, &s); + if (s) free (s); + } } -void -cut_menu_cb (GtkMenuItem *menuitem, gpointer user_data) -{ - /* #### */ - warning_dialog (GTK_WIDGET (menuitem), - "Error:\n\n" - "cut unimplemented\n", 1); -} - + +/* Button callbacks + */ void -copy_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - /* #### */ - warning_dialog (GTK_WIDGET (menuitem), - "Error:\n\n" - "copy unimplemented\n", 1); + state *s = global_state_kludge; /* I hate C so much... */ + flush_dialog_changes_and_save (s); + kill_preview_subproc (s, False); + gtk_main_quit (); } - -void -paste_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +static gboolean +wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { - /* #### */ - warning_dialog (GTK_WIDGET (menuitem), - "Error:\n\n" - "paste unimplemented\n", 1); + state *s = (state *) data; + flush_dialog_changes_and_save (s); + gtk_main_quit (); + return TRUE; } void about_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - char buf [2048]; - char *s = strdup (screensaver_id + 4); - char *s2; - - s2 = strchr (s, ','); - *s2 = 0; - s2 += 2; + char msg [2048]; + char *vers = strdup (screensaver_id + 4); + char *s; + char copy[1024]; + char *desc = _("For updates, check http://www.jwz.org/xscreensaver/"); - sprintf (buf, "%s\n%s\n\n" - "For updates, check http://www.jwz.org/xscreensaver/", - s, s2); - free (s); + s = strchr (vers, ','); + *s = 0; + s += 2; - warning_dialog (GTK_WIDGET (menuitem), buf, 100); + /* Ole Laursen says "don't use _() here because + non-ASCII characters aren't allowed in localizable string keys." + (I don't want to just use (c) instead of © because that doesn't + look as good in the plain-old default Latin1 "C" locale.) + */ +#ifdef HAVE_GTK2 + sprintf(copy, ("Copyright \xC2\xA9 1991-2004 %s"), s); +#else /* !HAVE_GTK2 */ + sprintf(copy, ("Copyright \251 1991-2004 %s"), s); +#endif /* !HAVE_GTK2 */ + + sprintf (msg, "%s\n\n%s", copy, desc); + + /* I can't make gnome_about_new() work here -- it starts dying in + gdk_imlib_get_visual() under gnome_about_new(). If this worked, + then this might be the thing to do: + + #ifdef HAVE_CRAPPLET + { + const gchar *auth[] = { 0 }; + GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc, + "xscreensaver.xpm"); + gtk_widget_show (about); + } + #else / * GTK but not GNOME * / + ... + */ + { + GdkColormap *colormap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + + GtkWidget *dialog = gtk_dialog_new (); + GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok; + GtkWidget *parent = GTK_WIDGET (menuitem); + while (parent->parent) + parent = parent->parent; + + hbox = gtk_hbox_new (FALSE, 20); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + hbox, TRUE, TRUE, 0); + + colormap = gtk_widget_get_colormap (parent); + gdkpixmap = + gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL, + (gchar **) logo_180_xpm); + icon = gtk_pixmap_new (gdkpixmap, mask); + gtk_misc_set_padding (GTK_MISC (icon), 10, 10); + + gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + + label1 = gtk_label_new (vers); + gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75); + +#ifndef HAVE_GTK2 + GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style); + GTK_WIDGET (label1)->style->font = + gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style); +#endif /* HAVE_GTK2 */ + + label2 = gtk_label_new (msg); + gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25); + +#ifndef HAVE_GTK2 + GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style); + GTK_WIDGET (label2)->style->font = + gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font")); + gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style); +#endif /* HAVE_GTK2 */ + + hb = gtk_hbutton_box_new (); + + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), + hb, TRUE, TRUE, 0); + +#ifdef HAVE_GTK2 + ok = gtk_button_new_from_stock (GTK_STOCK_OK); +#else /* !HAVE_GTK2 */ + ok = gtk_button_new_with_label (_("OK")); +#endif /* !HAVE_GTK2 */ + gtk_container_add (GTK_CONTAINER (hb), ok); + + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); + gtk_window_set_title (GTK_WINDOW (dialog), progclass); + + gtk_widget_show (hbox); + gtk_widget_show (icon); + gtk_widget_show (vbox); + gtk_widget_show (label1); + gtk_widget_show (label2); + gtk_widget_show (hb); + gtk_widget_show (ok); + gtk_widget_show (dialog); + + gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", + GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), + (gpointer) dialog); + gdk_window_set_transient_for (GTK_WIDGET (dialog)->window, + GTK_WIDGET (parent)->window); + gdk_window_show (GTK_WIDGET (dialog)->window); + gdk_window_raise (GTK_WIDGET (dialog)->window); + } } void doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ - - saver_preferences *p = pair->a; + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; char *help_command; if (!p->help_url || !*p->help_url) { - warning_dialog (GTK_WIDGET (menuitem), - "Error:\n\n" - "No Help URL has been specified.\n", 100); + warning_dialog (s->toplevel_widget, + _("Error:\n\n" + "No Help URL has been specified.\n"), False, 100); return; } @@ -426,225 +795,353 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data) void activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - int status = xscreensaver_command (gdk_display, XA_ACTIVATE, 0, False); - if (status < 0) - { - char buf [255]; - sprintf (buf, - "Error:\n\n" - "The ACTIVATE command failed (%d).\n", status); - warning_dialog (GTK_WIDGET (menuitem), buf, 100); - } + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_ACTIVATE, 0); } void lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - int status = xscreensaver_command (gdk_display, XA_LOCK, 0, False); - if (status < 0) - { - char buf [255]; - sprintf (buf, - "Error:\n\n" - "The LOCK command failed (%d).\n", status); - warning_dialog (GTK_WIDGET (menuitem), buf, 100); - } + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_LOCK, 0); } void kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { - int status = xscreensaver_command (gdk_display, XA_EXIT, 0, False); - if (status < 0) - { - char buf [255]; - sprintf (buf, - "Error:\n\n" - "The EXIT command failed (%d).\n", status); - warning_dialog (GTK_WIDGET (menuitem), buf, 100); - } + state *s = global_state_kludge; /* I hate C so much... */ + run_cmd (s, XA_EXIT, 0); } void -restart_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +restart_menu_cb (GtkWidget *widget, gpointer user_data) { -#if 0 - int status = xscreensaver_command (gdk_display, XA_RESTART, 0, False); - if (status < 0) - { - char buf [255]; - sprintf (buf, - "Error:\n\n" - "The RESTART command failed (%d).\n", status); - warning_dialog (GTK_WIDGET (menuitem), buf, 100); - } -#else - xscreensaver_command (gdk_display, XA_EXIT, 0, False); + state *s = global_state_kludge; /* I hate C so much... */ + flush_dialog_changes_and_save (s); + xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL); sleep (1); system ("xscreensaver -nosplash &"); -#endif -} - -static int -selected_hack_number (GtkWidget *toplevel) -{ - GtkViewport *vp = GTK_VIEWPORT (name_to_widget (toplevel, "viewport")); - GtkList *list_widget = GTK_LIST (GTK_BIN(vp)->child); - GList *slist = list_widget->selection; - GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0); - int which = (selected - ? gtk_list_child_position (list_widget, GTK_WIDGET (selected)) - : -1); - return which; + await_xscreensaver (s); } - -void -apply_this_cb (GtkButton *button, gpointer user_data) +static void +await_xscreensaver (state *s) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ + int countdown = 5; - saver_preferences *p = pair->a; - GtkList *list_widget = - GTK_LIST (name_to_widget (GTK_WIDGET (button), "list")); - int which = selected_hack_number (GTK_WIDGET (button)); + Display *dpy = GDK_DISPLAY(); + /* GtkWidget *dialog = 0;*/ + char *rversion = 0; - GtkEntry *cmd = GTK_ENTRY (name_to_widget (GTK_WIDGET (button), "cmd_text")); - GtkToggleButton *enabled = - GTK_TOGGLE_BUTTON (name_to_widget (GTK_WIDGET (button), "enabled")); - GtkCombo *vis = GTK_COMBO (name_to_widget (GTK_WIDGET (button), - "visual_combo")); + while (!rversion && (--countdown > 0)) + { + /* Check for the version of the running xscreensaver... */ + server_xscreensaver_version (dpy, &rversion, 0, 0); - Bool enabled_p = gtk_toggle_button_get_active (enabled); - const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry)); - const char *command = gtk_entry_get_text (cmd); - - char c; - unsigned long id; + /* If it's not there yet, wait a second... */ + if (!rversion) + sleep (1); + } - if (which < 0) return; +/* if (dialog) gtk_widget_destroy (dialog);*/ - /* Sanity-check and canonicalize whatever the user typed into the combo box. - */ - if (!strcasecmp (visual, "any")) visual = ""; - if (!strcasecmp (visual, "default")) visual = "Default"; - else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; - else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; - else if (!strcasecmp (visual, "best")) visual = "Best"; - else if (!strcasecmp (visual, "mono")) visual = "Mono"; - else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; - else if (!strcasecmp (visual, "gray")) visual = "Gray"; - else if (!strcasecmp (visual, "grey")) visual = "Gray"; - else if (!strcasecmp (visual, "color")) visual = "Color"; - else if (!strcasecmp (visual, "gl")) visual = "GL"; - else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; - else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; - else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor"; - else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; - else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; - else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; - else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; - else if (1 == sscanf (visual, " %ld %c", &id, &c)) ; - else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; + if (rversion) + { + /* Got it. */ + free (rversion); + } else { - gdk_beep (); /* unparsable */ - visual = ""; - gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any"); + /* Timed out, no screensaver running. */ + + char buf [1024]; + Bool root_p = (geteuid () == 0); + + strcpy (buf, + _("Error:\n\n" + "The xscreensaver daemon did not start up properly.\n" + "\n")); + + if (root_p) + +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than + the length ISO C89 compilers are required to + support" in the following expression... */ +# endif + strcat (buf, + _("You are running as root. This usually means that xscreensaver\n" + "was unable to contact your X server because access control is\n" + "turned on. Try running this command:\n" + "\n" + " xhost +localhost\n" + "\n" + "and then selecting `File / Restart Daemon'.\n" + "\n" + "Note that turning off access control will allow anyone logged\n" + "on to this machine to access your screen, which might be\n" + "considered a security problem. Please read the xscreensaver\n" + "manual and FAQ for more information.\n" + "\n" + "You shouldn't run X as root. Instead, you should log in as a\n" + "normal user, and `su' as necessary.")); + else + strcat (buf, _("Please check your $PATH and permissions.")); + + warning_dialog (s->toplevel_widget, buf, False, 1); } +} + + +static int +selected_list_element (state *s) +{ + return s->_selected_list_element; +} - p->screenhacks[which]->enabled_p = enabled_p; - if (p->screenhacks[which]->visual) - free (p->screenhacks[which]->visual); - if (p->screenhacks[which]->command) - free (p->screenhacks[which]->command); - p->screenhacks[which]->visual = strdup (visual); - p->screenhacks[which]->command = strdup (command); - ensure_selected_item_visible (GTK_WIDGET (list_widget)); +static int +demo_write_init_file (state *s, saver_preferences *p) +{ + +#if 0 + /* #### try to figure out why shit keeps getting reordered... */ + if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow")) + abort(); +#endif - write_init_file (p, short_version); + if (!write_init_file (p, s->short_version, False)) + { + if (s->debug_p) + fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name()); + return 0; + } + else + { + const char *f = init_file_name(); + if (!f || !*f) + warning_dialog (s->toplevel_widget, + _("Error:\n\nCouldn't determine init file name!\n"), + False, 100); + else + { + char *b = (char *) malloc (strlen(f) + 1024); + sprintf (b, _("Error:\n\nCouldn't write %s\n"), f); + warning_dialog (s->toplevel_widget, b, False, 100); + free (b); + } + return -1; + } } void run_this_cb (GtkButton *button, gpointer user_data) { - int which = selected_hack_number (GTK_WIDGET (button)); - if (which < 0) return; - apply_this_cb (button, user_data); - run_hack (which); + state *s = global_state_kludge; /* I hate C so much... */ + int list_elt = selected_list_element (s); + if (list_elt < 0) return; + if (!flush_dialog_changes_and_save (s)) + run_hack (s, list_elt, True); } void -cancel_this_cb (GtkButton *button, gpointer user_data) +manual_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); + int hack_number; + char *name, *name2, *cmd, *str; + if (list_elt < 0) return; + hack_number = s->list_elt_to_hack_number[list_elt]; + + flush_dialog_changes_and_save (s); + ensure_selected_item_visible (list_widget); + + name = strdup (p->screenhacks[hack_number]->command); + name2 = name; + while (isspace (*name2)) name2++; + str = name2; + while (*str && !isspace (*str)) str++; + *str = 0; + str = strrchr (name2, '/'); + if (str) name = str+1; + + cmd = get_string_resource ("manualCommand", "ManualCommand"); + if (cmd) + { + char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100); + strcpy (cmd2, "( "); + sprintf (cmd2 + strlen (cmd2), + cmd, + name2, name2, name2, name2); + strcat (cmd2, " ) &"); + system (cmd2); + free (cmd2); + } + else + { + warning_dialog (GTK_WIDGET (button), + _("Error:\n\nno `manualCommand' resource set."), + False, 100); + } + + free (name); +} + + +static void +force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ - GtkList *list_widget = - GTK_LIST (name_to_widget (GTK_WIDGET (button), "list")); - int which = selected_hack_number (GTK_WIDGET (button)); - if (which < 0) return; - ensure_selected_item_visible (GTK_WIDGET (list_widget)); - populate_demo_window (GTK_WIDGET (button), which, pair); + GtkWidget *parent = name_to_widget (s, "scroller"); + Bool was = GTK_WIDGET_IS_SENSITIVE (parent); +#ifdef HAVE_GTK2 + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeSelection *selection; +#endif /* HAVE_GTK2 */ + + if (!was) gtk_widget_set_sensitive (parent, True); +#ifdef HAVE_GTK2 + model = gtk_tree_view_get_model (GTK_TREE_VIEW (list)); + STFU g_assert (model); + gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list)); + gtk_tree_selection_select_iter (selection, &iter); +#else /* !HAVE_GTK2 */ + gtk_list_select_item (GTK_LIST (list), list_elt); +#endif /* !HAVE_GTK2 */ + if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list)); + if (!was) gtk_widget_set_sensitive (parent, False); } void run_next_cb (GtkButton *button, gpointer user_data) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ - saver_preferences *p = pair->a; + state *s = global_state_kludge; /* I hate C so much... */ + /* saver_preferences *p = &s->prefs; */ + Bool ops = s->preview_suppressed_p; - GtkList *list_widget = - GTK_LIST (name_to_widget (GTK_WIDGET (button), "list")); - int which = selected_hack_number (GTK_WIDGET (button)); + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); - if (which < 0) - which = 0; + if (list_elt < 0) + list_elt = 0; else - which++; + list_elt++; + + if (list_elt >= s->list_count) + list_elt = 0; - if (which >= p->screenhacks_count) - which = 0; + s->preview_suppressed_p = True; - gtk_list_select_item (GTK_LIST (list_widget), which); - ensure_selected_item_visible (GTK_WIDGET (list_widget)); - populate_demo_window (GTK_WIDGET (button), which, pair); - run_hack (which); + flush_dialog_changes_and_save (s); + force_list_select_item (s, list_widget, list_elt, True); + populate_demo_window (s, list_elt); + run_hack (s, list_elt, False); + + s->preview_suppressed_p = ops; } void run_prev_cb (GtkButton *button, gpointer user_data) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ - saver_preferences *p = pair->a; + state *s = global_state_kludge; /* I hate C so much... */ + /* saver_preferences *p = &s->prefs; */ + Bool ops = s->preview_suppressed_p; - GtkList *list_widget = - GTK_LIST (name_to_widget (GTK_WIDGET (button), "list")); - int which = selected_hack_number (GTK_WIDGET (button)); + GtkWidget *list_widget = name_to_widget (s, "list"); + int list_elt = selected_list_element (s); - if (which < 0) - which = p->screenhacks_count - 1; + if (list_elt < 0) + list_elt = s->list_count - 1; else - which--; + list_elt--; + + if (list_elt < 0) + list_elt = s->list_count - 1; + + s->preview_suppressed_p = True; + + flush_dialog_changes_and_save (s); + force_list_select_item (s, list_widget, list_elt, True); + populate_demo_window (s, list_elt); + run_hack (s, list_elt, False); + + s->preview_suppressed_p = ops; +} + + +/* Writes the given settings into prefs. + Returns true if there was a change, False otherwise. + command and/or visual may be 0, or enabled_p may be -1, meaning "no change". + */ +static Bool +flush_changes (state *s, + int list_elt, + int enabled_p, + const char *command, + const char *visual) +{ + saver_preferences *p = &s->prefs; + Bool changed = False; + screenhack *hack; + int hack_number; + if (list_elt < 0 || list_elt >= s->list_count) + abort(); + + hack_number = s->list_elt_to_hack_number[list_elt]; + hack = p->screenhacks[hack_number]; + + if (enabled_p != -1 && + enabled_p != hack->enabled_p) + { + hack->enabled_p = enabled_p; + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": enabled => %d\n", + blurb(), hack->name, enabled_p); + } + + if (command) + { + if (!hack->command || !!strcmp (command, hack->command)) + { + if (hack->command) free (hack->command); + hack->command = strdup (command); + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": command => \"%s\"\n", + blurb(), hack->name, command); + } + } - if (which < 0) - which = p->screenhacks_count - 1; + if (visual) + { + const char *ov = hack->visual; + if (!ov || !*ov) ov = "any"; + if (!*visual) visual = "any"; + if (!!strcasecmp (visual, ov)) + { + if (hack->visual) free (hack->visual); + hack->visual = strdup (visual); + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n", + blurb(), hack->name, visual); + } + } - gtk_list_select_item (GTK_LIST (list_widget), which); - ensure_selected_item_visible (GTK_WIDGET (list_widget)); - populate_demo_window (GTK_WIDGET (button), which, pair); - run_hack (which); + return changed; } @@ -652,692 +1149,2640 @@ run_prev_cb (GtkButton *button, gpointer user_data) this parses the text, and does error checking. */ static void -hack_time_text (const char *line, Time *store, Bool sec_p) +hack_time_text (state *s, const char *line, Time *store, Bool sec_p) { if (*line) { int value; - value = parse_time ((char *) line, sec_p, True); - value *= 1000; /* Time measures in microseconds */ + if (!sec_p || strchr (line, ':')) + value = parse_time ((char *) line, sec_p, True); + else + { + char c; + if (sscanf (line, "%d%c", &value, &c) != 1) + value = -1; + if (!sec_p) + value *= 60; + } + + value *= 1000; /* Time measures in microseconds */ if (value < 0) - /* gdk_beep () */; + { + char b[255]; + sprintf (b, + _("Error:\n\n" + "Unparsable time format: \"%s\"\n"), + line); + warning_dialog (s->toplevel_widget, b, False, 100); + } else *store = value; } } -void -prefs_ok_cb (GtkButton *button, gpointer user_data) -{ - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ - - saver_preferences *p = pair->a; - saver_preferences *p2 = pair->b; - -# define SECONDS(field, name) \ - hack_time_text (gtk_entry_get_text (\ - GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \ - (field), \ - True) - -# define MINUTES(field, name) \ - hack_time_text (gtk_entry_get_text (\ - GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \ - (field), \ - False) - -# define INTEGER(field, name) do { \ - char *line = gtk_entry_get_text (\ - GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))); \ - unsigned int value; \ - char c; \ - if (! *line) \ - ; \ - else if (sscanf (line, "%u%c", &value, &c) != 1) \ - gdk_beep(); \ - else \ - *(field) = value; \ - } while(0) - - MINUTES (&p2->timeout, "timeout_text"); - MINUTES (&p2->cycle, "cycle_text"); - SECONDS (&p2->fade_seconds, "fade_text"); - INTEGER (&p2->fade_ticks, "ticks_text"); - MINUTES (&p2->lock_timeout, "lock_text"); - SECONDS (&p2->passwd_timeout, "pass_text"); - -#undef SECONDS -#undef MINUTES -#undef INTEGER - - p->timeout = p2->timeout; - p->cycle = p2->cycle; - p->lock_timeout = p2->lock_timeout; - p->passwd_timeout = p2->passwd_timeout; - p->fade_seconds = p2->fade_seconds; - p->fade_ticks = p2->fade_ticks; - p->verbose_p = p2->verbose_p; - p->install_cmap_p = p2->install_cmap_p; - p->fade_p = p2->fade_p; - p->unfade_p = p2->unfade_p; - p->lock_p = p2->lock_p; - - populate_prefs_page (GTK_WIDGET (button), pair); - - write_init_file (p, short_version); +static Bool +directory_p (const char *path) +{ + struct stat st; + if (!path || !*path) + return False; + else if (stat (path, &st)) + return False; + else if (!S_ISDIR (st.st_mode)) + return False; + else + return True; } - -void -prefs_cancel_cb (GtkButton *button, gpointer user_data) +static char * +normalize_directory (const char *path) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ + int L; + char *p2, *s; + if (!path || !*path) return 0; + L = strlen (path); + p2 = (char *) malloc (L + 2); + strcpy (p2, path); + if (p2[L-1] == '/') /* remove trailing slash */ + p2[--L] = 0; + + for (s = p2; s && *s; s++) + { + if (*s == '/' && + (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */ + !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */ + { + char *s0 = s; + while (s0 > p2 && s0[-1] != '/') + s0--; + if (s0 > p2) + { + s0--; + s += 3; + strcpy (s0, s); + s = s0-1; + } + } + else if (*s == '/' && !strncmp (s, "/./", 3)) /* delete "/./" */ + strcpy (s, s+2), s--; + else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */ + *s = 0, s--; + } - *pair->b = *pair->a; - populate_prefs_page (GTK_WIDGET (button), pair); + for (s = p2; s && *s; s++) /* normalize consecutive slashes */ + while (s[0] == '/' && s[1] == '/') + strcpy (s, s+1); + + /* and strip trailing whitespace for good measure. */ + L = strlen(p2); + while (isspace(p2[L-1])) + p2[--L] = 0; + + return p2; } -static gint -list_doubleclick_cb (GtkWidget *button, GdkEventButton *event, - gpointer client_data) +#ifdef HAVE_GTK2 + +typedef struct { + state *s; + int i; + Bool *changed; +} FlushForeachClosure; + +static gboolean +flush_checkbox (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { - if (event->type == GDK_2BUTTON_PRESS) - { - GtkList *list = GTK_LIST (name_to_widget (button, "list")); - int which = gtk_list_child_position (list, GTK_WIDGET (button)); + FlushForeachClosure *closure = data; + gboolean checked; - if (which >= 0) - run_hack (which); - } + gtk_tree_model_get (model, iter, + COL_ENABLED, &checked, + -1); + + if (flush_changes (closure->s, closure->i, + checked, 0, 0)) + *closure->changed = True; + + closure->i++; + /* don't remove row */ return FALSE; } +#endif /* HAVE_GTK2 */ -static void -list_select_cb (GtkList *list, GtkWidget *child) +/* Flush out any changes made in the main dialog window (where changes + take place immediately: clicking on a checkbox causes the init file + to be written right away.) + */ +static Bool +flush_dialog_changes_and_save (state *s) { - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + saver_preferences P2, *p2 = &P2; +#ifdef HAVE_GTK2 + GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeModel *model = gtk_tree_view_get_model (list_widget); + FlushForeachClosure closure; +#else /* !HAVE_GTK2 */ + GtkList *list_widget = GTK_LIST (name_to_widget (s, "list")); + GList *kids = gtk_container_children (GTK_CONTAINER (list_widget)); + int i; +#endif /* !HAVE_GTK2 */ - int which = gtk_list_child_position (list, GTK_WIDGET (child)); - populate_demo_window (GTK_WIDGET (list), which, pair); -} + Bool changed = False; + GtkWidget *w; -static void -list_unselect_cb (GtkList *list, GtkWidget *child) -{ - /* prefs_pair *pair = (prefs_pair *) client_data; */ - prefs_pair *pair = global_prefs_pair; /* I hate C so much... */ + if (s->saving_p) return False; + s->saving_p = True; - populate_demo_window (GTK_WIDGET (list), -1, pair); -} + *p2 = *p; - -/* Populating the various widgets - */ + /* Flush any checkbox changes in the list down into the prefs struct. + */ +#ifdef HAVE_GTK2 + closure.s = s; + closure.changed = &changed; + closure.i = 0; + gtk_tree_model_foreach (model, flush_checkbox, &closure); +#else /* !HAVE_GTK2 */ -/* Formats a `Time' into "H:MM:SS". (Time is microseconds.) - */ -static void -format_time (char *buf, Time time) -{ - int s = time / 1000; - unsigned int h = 0, m = 0; - if (s >= 60) + for (i = 0; kids; kids = kids->next, i++) + { + GtkWidget *line = GTK_WIDGET (kids->data); + GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child); + GtkWidget *line_check = + GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data); + Bool checked = + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check)); + + if (flush_changes (s, i, (checked ? 1 : 0), 0, 0)) + changed = True; + } +#endif /* ~HAVE_GTK2 */ + + /* Flush the non-hack-specific settings down into the prefs struct. + */ + +# define SECONDS(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True) + +# define MINUTES(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False) + +# define CHECKBOX(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)) + +# define PATHNAME(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w))) + + MINUTES (&p2->timeout, "timeout_spinbutton"); + MINUTES (&p2->cycle, "cycle_spinbutton"); + CHECKBOX (p2->lock_p, "lock_button"); + MINUTES (&p2->lock_timeout, "lock_spinbutton"); + + CHECKBOX (p2->dpms_enabled_p, "dpms_button"); + MINUTES (&p2->dpms_standby, "dpms_standby_spinbutton"); + MINUTES (&p2->dpms_suspend, "dpms_suspend_spinbutton"); + MINUTES (&p2->dpms_off, "dpms_off_spinbutton"); + + CHECKBOX (p2->grab_desktop_p, "grab_desk_button"); + CHECKBOX (p2->grab_video_p, "grab_video_button"); + CHECKBOX (p2->random_image_p, "grab_image_button"); + PATHNAME (p2->image_directory, "image_text"); + + CHECKBOX (p2->verbose_p, "verbose_button"); + CHECKBOX (p2->capture_stderr_p, "capture_button"); + CHECKBOX (p2->splash_p, "splash_button"); + + CHECKBOX (p2->install_cmap_p, "install_button"); + CHECKBOX (p2->fade_p, "fade_button"); + CHECKBOX (p2->unfade_p, "unfade_button"); + SECONDS (&p2->fade_seconds, "fade_spinbutton"); + +# undef SECONDS +# undef MINUTES +# undef CHECKBOX +# undef PATHNAME + + /* Warn if the image directory doesn't exist. + */ + if (p2->image_directory && + *p2->image_directory && + !directory_p (p2->image_directory)) + { + char b[255]; + sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n", + p2->image_directory); + warning_dialog (s->toplevel_widget, b, False, 100); + } + + + /* Map the mode menu to `saver_mode' enum values. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt)); + GtkWidget *selected = gtk_menu_get_active (menu); + GList *kids = gtk_container_children (GTK_CONTAINER (menu)); + int menu_elt = g_list_index (kids, (gpointer) selected); + if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort(); + p2->mode = mode_menu_order[menu_elt]; + } + + if (p2->mode == ONE_HACK) { - m += (s / 60); - s %= 60; + int list_elt = selected_list_element (s); + p2->selected_hack = (list_elt >= 0 + ? s->list_elt_to_hack_number[list_elt] + : -1); } - if (m >= 60) + +# define COPY(field, name) \ + if (p->field != p2->field) { \ + changed = True; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \ + } \ + p->field = p2->field + + COPY(mode, "mode"); + COPY(selected_hack, "selected_hack"); + + COPY(timeout, "timeout"); + COPY(cycle, "cycle"); + COPY(lock_p, "lock_p"); + COPY(lock_timeout, "lock_timeout"); + + COPY(dpms_enabled_p, "dpms_enabled_p"); + COPY(dpms_standby, "dpms_standby"); + COPY(dpms_suspend, "dpms_suspend"); + COPY(dpms_off, "dpms_off"); + + COPY(verbose_p, "verbose_p"); + COPY(capture_stderr_p, "capture_stderr_p"); + COPY(splash_p, "splash_p"); + + COPY(install_cmap_p, "install_cmap_p"); + COPY(fade_p, "fade_p"); + COPY(unfade_p, "unfade_p"); + COPY(fade_seconds, "fade_seconds"); + + COPY(grab_desktop_p, "grab_desktop_p"); + COPY(grab_video_p, "grab_video_p"); + COPY(random_image_p, "random_image_p"); + +# undef COPY + + if (!p->image_directory || + !p2->image_directory || + strcmp(p->image_directory, p2->image_directory)) + { + changed = True; + if (s->debug_p) + fprintf (stderr, "%s: image_directory => \"%s\"\n", + blurb(), p2->image_directory); + } + if (p->image_directory && p->image_directory != p2->image_directory) + free (p->image_directory); + p->image_directory = p2->image_directory; + p2->image_directory = 0; + + populate_prefs_page (s); + + if (changed) { - h += (m / 60); - m %= 60; + Display *dpy = GDK_DISPLAY(); + Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK); + sync_server_dpms_settings (dpy, enabled_p, + p->dpms_standby / 1000, + p->dpms_suspend / 1000, + p->dpms_off / 1000, + False); + + changed = demo_write_init_file (s, p); } - sprintf (buf, "%u:%02u:%02u", h, m, s); + + s->saving_p = False; + return changed; } -static char * -make_pretty_name (const char *shell_command) +/* Flush out any changes made in the popup dialog box (where changes + take place only when the OK button is clicked.) + */ +static Bool +flush_popup_changes_and_save (state *s) { - char *s = strdup (shell_command); - char *s2; - char res_name[255]; + Bool changed = False; + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); - for (s2 = s; *s2; s2++) /* truncate at first whitespace */ - if (isspace (*s2)) - { - *s2 = 0; - break; - } + GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); + GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo")); + + const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry)); + const char *command = gtk_entry_get_text (cmd); + + char c; + unsigned long id; + + if (s->saving_p) return False; + s->saving_p = True; + + if (list_elt < 0) + goto DONE; - s2 = strrchr (s, '/'); /* if pathname, take last component */ - if (s2) + if (maybe_reload_init_file (s) != 0) { - s2 = strdup (s2+1); - free (s); - s = s2; + changed = True; + goto DONE; } - if (strlen (s) > 50) /* 51 is hereby defined as "unreasonable" */ - s[50] = 0; + /* Sanity-check and canonicalize whatever the user typed into the combo box. + */ + if (!strcasecmp (visual, "")) visual = ""; + else if (!strcasecmp (visual, "any")) visual = ""; + else if (!strcasecmp (visual, "default")) visual = "Default"; + else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; + else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; + else if (!strcasecmp (visual, "best")) visual = "Best"; + else if (!strcasecmp (visual, "mono")) visual = "Mono"; + else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; + else if (!strcasecmp (visual, "gray")) visual = "Gray"; + else if (!strcasecmp (visual, "grey")) visual = "Gray"; + else if (!strcasecmp (visual, "color")) visual = "Color"; + else if (!strcasecmp (visual, "gl")) visual = "GL"; + else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; + else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; + else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor"; + else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; + else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; + else if (1 == sscanf (visual, " %lu %c", &id, &c)) ; + else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; + else + { + gdk_beep (); /* unparsable */ + visual = ""; + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any")); + } - sprintf (res_name, "hacks.%s.name", s); /* resource? */ - s2 = get_string_resource (res_name, res_name); - if (s2) - return s2; + changed = flush_changes (s, list_elt, -1, command, visual); + if (changed) + { + changed = demo_write_init_file (s, p); - for (s2 = s; *s2; s2++) /* if it has any capitals, return it */ - if (*s2 >= 'A' && *s2 <= 'Z') - return s; + /* Do this to re-launch the hack if (and only if) the command line + has changed. */ + populate_demo_window (s, selected_list_element (s)); + } - if (s[0] >= 'a' && s[0] <= 'z') /* else cap it */ - s[0] -= 'a'-'A'; - if (s[0] == 'X' && s[1] >= 'a' && s[1] <= 'z') /* (magic leading X) */ - s[1] -= 'a'-'A'; - return s; + DONE: + s->saving_p = False; + return changed; } -/* Finds the number of the last hack to run, and makes that item be - selected by default. +void +pref_changed_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + if (! s->initializing_p) + { + s->initializing_p = True; + flush_dialog_changes_and_save (s); + s->initializing_p = False; + } +} + +gboolean +pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + pref_changed_cb (widget, user_data); + return FALSE; +} + +/* Callback on menu items in the "mode" options menu. */ -static void -scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair) +void +mode_menu_item_cb (GtkWidget *widget, gpointer user_data) { - Atom type; - int format; - unsigned long nitems, bytesafter; - CARD32 *data = 0; - Display *dpy = gdk_display; - int which = 0; - GtkList *list; + state *s = (state *) user_data; + saver_preferences *p = &s->prefs; + GtkWidget *list = name_to_widget (s, "list"); + int list_elt; - if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ - XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, - &type, &format, &nitems, &bytesafter, - (unsigned char **) &data) - == Success - && type == XA_INTEGER - && nitems >= 3 - && data) - which = (int) data[2] - 1; + GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent)); + int menu_index = 0; + saver_mode new_mode; + + while (menu_items) + { + if (menu_items->data == widget) + break; + menu_index++; + menu_items = menu_items->next; + } + if (!menu_items) abort(); - if (data) free (data); + new_mode = mode_menu_order[menu_index]; - if (which < 0) - return; + /* Keep the same list element displayed as before; except if we're + switching *to* "one screensaver" mode from any other mode, set + "the one" to be that which is currently selected. + */ + list_elt = selected_list_element (s); + if (new_mode == ONE_HACK) + p->selected_hack = s->list_elt_to_hack_number[list_elt]; - list = GTK_LIST (name_to_widget (toplevel, "list")); - gtk_list_select_item (list, which); - ensure_selected_item_visible (GTK_WIDGET (list)); - populate_demo_window (toplevel, which, pair); + { + saver_mode old_mode = p->mode; + p->mode = new_mode; + populate_demo_window (s, list_elt); + force_list_select_item (s, list, list_elt, True); + p->mode = old_mode; /* put it back, so the init file gets written */ + } + + pref_changed_cb (widget, user_data); } +void +switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, + gint page_num, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + pref_changed_cb (GTK_WIDGET (notebook), user_data); + + /* If we're switching to page 0, schedule the current hack to be run. + Otherwise, schedule it to stop. */ + if (page_num == 0) + populate_demo_window (s, selected_list_element (s)); + else + schedule_preview (s, 0); +} +#ifdef HAVE_GTK2 static void -populate_hack_list (GtkWidget *toplevel, prefs_pair *pair) +list_activated_cb (GtkTreeView *list, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer data) { - saver_preferences *p = pair->a; - GtkList *list = GTK_LIST (name_to_widget (toplevel, "list")); - screenhack **hacks = p->screenhacks; - screenhack **h; + state *s = data; + char *str; + int list_elt; - for (h = hacks; *h; h++) - { - GtkWidget *line; - char *pretty_name = (h[0]->name - ? strdup (h[0]->name) - : make_pretty_name (h[0]->command)); + STFU g_return_if_fail (!gdk_pointer_is_grabbed ()); - line = gtk_list_item_new_with_label (pretty_name); - free (pretty_name); + str = gtk_tree_path_to_string (path); + list_elt = strtol (str, NULL, 10); + g_free (str); - gtk_container_add (GTK_CONTAINER (list), line); - gtk_signal_connect (GTK_OBJECT (line), "button_press_event", - GTK_SIGNAL_FUNC (list_doubleclick_cb), - (gpointer) pair); -#if 0 /* #### */ - GTK_WIDGET (GTK_BIN(line)->child)->style = - gtk_style_copy (GTK_WIDGET (text_line)->style); -#endif - gtk_widget_show (line); - } + if (list_elt >= 0) + run_hack (s, list_elt, True); +} - gtk_signal_connect (GTK_OBJECT (list), "select_child", - GTK_SIGNAL_FUNC (list_select_cb), - (gpointer) pair); - gtk_signal_connect (GTK_OBJECT (list), "unselect_child", - GTK_SIGNAL_FUNC (list_unselect_cb), - (gpointer) pair); +static void +list_select_changed_cb (GtkTreeSelection *selection, gpointer data) +{ + state *s = (state *)data; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + char *str; + int list_elt; + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return; + + path = gtk_tree_model_get_path (model, &iter); + str = gtk_tree_path_to_string (path); + list_elt = strtol (str, NULL, 10); + + gtk_tree_path_free (path); + g_free (str); + + populate_demo_window (s, list_elt); + flush_dialog_changes_and_save (s); } +#else /* !HAVE_GTK2 */ -static void -populate_prefs_page (GtkWidget *top, prefs_pair *pair) -{ - saver_preferences *p = pair->a; - char s[100]; - - format_time (s, p->timeout); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "timeout_text")), s); - format_time (s, p->cycle); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "cycle_text")), s); - format_time (s, p->lock_timeout); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "lock_text")), s); - format_time (s, p->passwd_timeout); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "pass_text")), s); - format_time (s, p->fade_seconds); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "fade_text")), s); - sprintf (s, "%u", p->fade_ticks); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "ticks_text")), s); - - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (name_to_widget (top, "verbose_button")), - p->verbose_p); - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (name_to_widget (top, "install_button")), - p->install_cmap_p); - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (name_to_widget (top, "fade_button")), - p->fade_p); - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (name_to_widget (top, "unfade_button")), - p->unfade_p); - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (name_to_widget (top, "lock_button")), - p->lock_p); +static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the + list_select_cb that comes in + *after* we've double-clicked. + */ +static gint +list_doubleclick_cb (GtkWidget *button, GdkEventButton *event, + gpointer data) +{ + state *s = (state *) data; + if (event->type == GDK_2BUTTON_PRESS) + { + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + int list_elt = gtk_list_child_position (list, GTK_WIDGET (button)); - { - Bool found_any_writable_cells = False; - Display *dpy = gdk_display; - int nscreens = ScreenCount(dpy); - int i; - for (i = 0; i < nscreens; i++) - { - Screen *s = ScreenOfDisplay (dpy, i); - if (has_writable_cells (s, DefaultVisualOfScreen (s))) - { - found_any_writable_cells = True; - break; - } - } + last_doubleclick_time = time ((time_t *) 0); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "fade_label")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "ticks_label")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "fade_text")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "ticks_text")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "install_button")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "fade_button")), - found_any_writable_cells); - gtk_widget_set_sensitive ( - GTK_WIDGET (name_to_widget (top, "unfade_button")), - found_any_writable_cells); - } + if (list_elt >= 0) + run_hack (s, list_elt, True); + } + return FALSE; } static void -sensitize_demo_widgets (GtkWidget *toplevel, Bool sensitive_p) +list_select_cb (GtkList *list, GtkWidget *child, gpointer data) { - const char *names[] = { "cmd_label", "cmd_text", "enabled", - "visual", "visual_combo", - "demo", "apply", "cancel" }; - int i; - for (i = 0; i < countof(names); i++) + state *s = (state *) data; + time_t now = time ((time_t *) 0); + + if (now >= last_doubleclick_time + 2) { - GtkWidget *w = name_to_widget (toplevel, names[i]); - gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + int list_elt = gtk_list_child_position (list, GTK_WIDGET (child)); + populate_demo_window (s, list_elt); + flush_dialog_changes_and_save (s); } +} - /* I don't know how to handle these yet... */ - { - const char *names2[] = { "cut_menu", "copy_menu", "paste_menu" }; - for (i = 0; i < countof(names2); i++) - { - GtkWidget *w = name_to_widget (toplevel, names2[i]); - gtk_widget_set_sensitive (GTK_WIDGET(w), False); - } - } +static void +list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data) +{ + state *s = (state *) data; + populate_demo_window (s, -1); + flush_dialog_changes_and_save (s); } +#endif /* !HAVE_GTK2 */ -/* Even though we've given these text fields a maximum number of characters, - their default size is still about 30 characters wide -- so measure out - a string in their font, and resize them to just fit that. + +/* Called when the checkboxes that are in the left column of the + scrolling list are clicked. This both populates the right pane + (just as clicking on the label (really, listitem) does) and + also syncs this checkbox with the right pane Enabled checkbox. */ static void -fix_text_entry_sizes (GtkWidget *toplevel) +list_checkbox_cb ( +#ifdef HAVE_GTK2 + GtkCellRendererToggle *toggle, + gchar *path_string, +#else /* !HAVE_GTK2 */ + GtkWidget *cb, +#endif /* !HAVE_GTK2 */ + gpointer data) { - const char *names[] = { "timeout_text", "cycle_text", "fade_text", - "ticks_text", "lock_text", "pass_text" }; - int i; - int width = 0; - GtkWidget *w; + state *s = (state *) data; + +#ifdef HAVE_GTK2 + GtkScrolledWindow *scroller = + GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller")); + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeModel *model = gtk_tree_view_get_model (list); + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + gboolean active; +#else /* !HAVE_GTK2 */ + GtkWidget *line_hbox = GTK_WIDGET (cb)->parent; + GtkWidget *line = GTK_WIDGET (line_hbox)->parent; + + GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent); + GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent); + GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); +#endif /* !HAVE_GTK2 */ + GtkAdjustment *adj; + double scroll_top; - for (i = 0; i < countof(names); i++) + int list_elt; + +#ifdef HAVE_GTK2 + if (!gtk_tree_model_get_iter (model, &iter, path)) { - w = GTK_WIDGET (name_to_widget (toplevel, names[i])); - if (width == 0) - width = gdk_text_width (w->style->font, "00:00:00_", 9); - gtk_widget_set_usize (w, width, -2); + g_warning ("bad path: %s", path_string); + return; } + gtk_tree_path_free (path); - /* Now fix the size of the combo box. - */ - w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "visual_combo")); - w = GTK_COMBO (w)->entry; - width = gdk_text_width (w->style->font, "PseudoColor___", 14); - gtk_widget_set_usize (w, width, -2); + gtk_tree_model_get (model, &iter, + COL_ENABLED, &active, + -1); -#if 0 - /* Now fix the size of the list. - */ - w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "list")); - width = gdk_text_width (w->style->font, "nnnnnnnnnnnnnnnnnnnnnn", 22); - gtk_widget_set_usize (w, width, -2); -#endif -} + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + COL_ENABLED, !active, + -1); + list_elt = strtol (path_string, NULL, 10); +#else /* !HAVE_GTK2 */ + list_elt = gtk_list_child_position (list, line); +#endif /* !HAVE_GTK2 */ + /* remember previous scroll position of the top of the list */ + adj = gtk_scrolled_window_get_vadjustment (scroller); + scroll_top = adj->value; - -/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...) - */ + flush_dialog_changes_and_save (s); + force_list_select_item (s, GTK_WIDGET (list), list_elt, False); + populate_demo_window (s, list_elt); + + /* restore the previous scroll position of the top of the list. + this is weak, but I don't really know why it's moving... */ + gtk_adjustment_set_value (adj, scroll_top); +} -static char *up_arrow_xpm[] = { - "15 15 4 1", - " c None", - "- c #FFFFFF", - "+ c #D6D6D6", - "@ c #000000", - " @ ", - " @ ", - " -+@ ", - " -+@ ", - " -+++@ ", - " -+++@ ", - " -+++++@ ", - " -+++++@ ", - " -+++++++@ ", - " -+++++++@ ", - " -+++++++++@ ", - " -+++++++++@ ", - " -+++++++++++@ ", - " @@@@@@@@@@@@@ ", - " ", +typedef struct { + state *state; + GtkFileSelection *widget; +} file_selection_data; - /* Need these here because gdk_pixmap_create_from_xpm_d() walks off - the end of the array (Gtk 1.2.5.) */ + + +static void +store_image_directory (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + state *s = fsd->state; + GtkFileSelection *selector = fsd->widget; + GtkWidget *top = s->toplevel_widget; + saver_preferences *p = &s->prefs; + const char *path = gtk_file_selection_get_filename (selector); + + if (p->image_directory && !strcmp(p->image_directory, path)) + return; /* no change */ + + if (!directory_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, False, 100); + return; + } + + if (p->image_directory) free (p->image_directory); + p->image_directory = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), + (p->image_directory ? p->image_directory : "")); + demo_write_init_file (s, p); +} + + +static void +browse_image_dir_cancel (GtkWidget *button, gpointer user_data) +{ + file_selection_data *fsd = (file_selection_data *) user_data; + gtk_widget_hide (GTK_WIDGET (fsd->widget)); +} + +static void +browse_image_dir_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_image_directory (button, user_data); +} + +static void +browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + browse_image_dir_cancel (widget, user_data); +} + + +void +browse_image_dir_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + saver_preferences *p = &s->prefs; + static file_selection_data *fsd = 0; + + GtkFileSelection *selector = GTK_FILE_SELECTION( + gtk_file_selection_new ("Please select the image directory.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->image_directory && *p->image_directory) + gtk_file_selection_set_filename (selector, p->image_directory); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector->cancel_button), + "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel), + (gpointer *) fsd); + gtk_signal_connect (GTK_OBJECT (selector), "delete_event", + GTK_SIGNAL_FUNC (browse_image_dir_close), + (gpointer *) fsd); + + gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False); + + gtk_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + +void +settings_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + int list_elt = selected_list_element (s); + + populate_demo_window (s, list_elt); /* reset the widget */ + populate_popup_window (s); /* create UI on popup window */ + gtk_widget_show (s->popup_widget); +} + +static void +settings_sync_cmd_text (state *s) +{ +# ifdef HAVE_XML + GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); + char *cmd_line = get_configurator_command_line (s->cdata); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line)); + free (cmd_line); +# endif /* HAVE_XML */ +} + +void +settings_adv_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + + settings_sync_cmd_text (s); + gtk_notebook_set_page (notebook, 1); +} + +void +settings_std_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + + /* Re-create UI to reflect the in-progress command-line settings. */ + populate_popup_window (s); + + gtk_notebook_set_page (notebook, 0); +} + +void +settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, + gint page_num, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkWidget *adv = name_to_widget (s, "adv_button"); + GtkWidget *std = name_to_widget (s, "std_button"); + + if (page_num == 0) + { + gtk_widget_show (adv); + gtk_widget_hide (std); + } + else if (page_num == 1) + { + gtk_widget_hide (adv); + gtk_widget_show (std); + } + else + abort(); +} + + + +void +settings_cancel_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + gtk_widget_hide (s->popup_widget); +} + +void +settings_ok_cb (GtkButton *button, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + int page = gtk_notebook_get_current_page (notebook); + + if (page == 0) + /* Regenerate the command-line from the widget contents before saving. + But don't do this if we're looking at the command-line page already, + or we will blow away what they typed... */ + settings_sync_cmd_text (s); + + flush_popup_changes_and_save (s); + gtk_widget_hide (s->popup_widget); +} + +static gboolean +wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + state *s = (state *) data; + settings_cancel_cb (0, (gpointer) s); + return TRUE; +} + + + +/* Populating the various widgets + */ + + +/* Returns the number of the last hack run by the server. + */ +static int +server_current_hack (void) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + Display *dpy = GDK_DISPLAY(); + int hack_number = -1; + + if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + 0, 3, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + CARD32 *data = (CARD32 *) dataP; + hack_number = (int) data[2] - 1; + } + + if (dataP) XFree (dataP); + + return hack_number; +} + + +/* Finds the number of the last hack to run, and makes that item be + selected by default. + */ +static void +scroll_to_current_hack (state *s) +{ + saver_preferences *p = &s->prefs; + int hack_number; + + if (p->mode == ONE_HACK) + hack_number = p->selected_hack; + else + hack_number = server_current_hack (); + + if (hack_number >= 0 && hack_number < p->screenhacks_count) + { + int list_elt = s->hack_number_to_list_elt[hack_number]; + GtkWidget *list = name_to_widget (s, "list"); + force_list_select_item (s, list, list_elt, True); + populate_demo_window (s, list_elt); + } +} + + +static Bool +on_path_p (const char *program) +{ + int result = False; + struct stat st; + char *cmd = strdup (program); + char *token = strchr (cmd, ' '); + char *path = 0; + int L; + + if (token) *token = 0; + token = 0; + + if (strchr (cmd, '/')) + { + result = (0 == stat (cmd, &st)); + goto DONE; + } + + path = getenv("PATH"); + if (!path || !*path) + goto DONE; + + L = strlen (cmd); + path = strdup (path); + token = strtok (path, ":"); + + while (token) + { + char *p2 = (char *) malloc (strlen (token) + L + 3); + strcpy (p2, token); + strcat (p2, "/"); + strcat (p2, cmd); + result = (0 == stat (p2, &st)); + if (result) + goto DONE; + token = strtok (0, ":"); + } + + DONE: + free (cmd); + if (path) free (path); + return result; +} + + +static void +populate_hack_list (state *s) +{ +#ifdef HAVE_GTK2 + saver_preferences *p = &s->prefs; + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkListStore *model; + GtkTreeSelection *selection; + GtkCellRenderer *ren; + GtkTreeIter iter; + int i; + + g_object_get (G_OBJECT (list), + "model", &model, + NULL); + if (!model) + { + model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING); + g_object_set (G_OBJECT (list), "model", model, NULL); + g_object_unref (model); + + ren = gtk_cell_renderer_toggle_new (); + gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED, + _("Use"), ren, + "active", COL_ENABLED, + NULL); + + g_signal_connect (ren, "toggled", + G_CALLBACK (list_checkbox_cb), + s); + + ren = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (list, COL_NAME, + _("Screen Saver"), ren, + "markup", COL_NAME, + NULL); + + g_signal_connect_after (list, "row_activated", + G_CALLBACK (list_activated_cb), + s); + + selection = gtk_tree_view_get_selection (list); + g_signal_connect (selection, "changed", + G_CALLBACK (list_select_changed_cb), + s); + + } + + for (i = 0; i < s->list_count; i++) + { + int hack_number = s->list_elt_to_hack_number[i]; + screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]); + char *pretty_name; + Bool available_p = (hack && s->hacks_available_p [hack_number]); + + if (!hack) continue; + + /* If we're to suppress uninstalled hacks, check $PATH now. */ + if (p->ignore_uninstalled_p && !available_p) + continue; + + pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (hack->command)); + + if (!available_p) + { + /* Make the text foreground be the color of insensitive widgets + (but don't actually make it be insensitive, since we still + want to be able to click on it.) + */ + GtkStyle *style = GTK_WIDGET (list)->style; + GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE]; + /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */ + char *buf = (char *) malloc (strlen (pretty_name) + 100); + + sprintf (buf, "%s", + fg->red >> 8, fg->green >> 8, fg->blue >> 8, + /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */ + pretty_name); + free (pretty_name); + pretty_name = buf; + } + + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + COL_ENABLED, hack->enabled_p, + COL_NAME, pretty_name, + -1); + free (pretty_name); + } + +#else /* !HAVE_GTK2 */ + + saver_preferences *p = &s->prefs; + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + int i; + for (i = 0; i < s->list_count; i++) + { + int hack_number = s->list_elt_to_hack_number[i]; + screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]); + + /* A GtkList must contain only GtkListItems, but those can contain + an arbitrary widget. We add an Hbox, and inside that, a Checkbox + and a Label. We handle single and double click events on the + line itself, for clicking on the text, but the interior checkbox + also handles its own events. + */ + GtkWidget *line; + GtkWidget *line_hbox; + GtkWidget *line_check; + GtkWidget *line_label; + char *pretty_name; + Bool available_p = (hack && s->hacks_available_p [hack_number]); + + if (!hack) continue; + + /* If we're to suppress uninstalled hacks, check $PATH now. */ + if (p->ignore_uninstalled_p && !available_p) + continue; + + pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (hack->command)); + + line = gtk_list_item_new (); + line_hbox = gtk_hbox_new (FALSE, 0); + line_check = gtk_check_button_new (); + line_label = gtk_label_new (pretty_name); + + gtk_container_add (GTK_CONTAINER (line), line_hbox); + gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check), + hack->enabled_p); + gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT); + + gtk_widget_show (line_check); + gtk_widget_show (line_label); + gtk_widget_show (line_hbox); + gtk_widget_show (line); + + free (pretty_name); + + gtk_container_add (GTK_CONTAINER (list), line); + gtk_signal_connect (GTK_OBJECT (line), "button_press_event", + GTK_SIGNAL_FUNC (list_doubleclick_cb), + (gpointer) s); + + gtk_signal_connect (GTK_OBJECT (line_check), "toggled", + GTK_SIGNAL_FUNC (list_checkbox_cb), + (gpointer) s); + + gtk_widget_show (line); + + if (!available_p) + { + /* Make the widget be colored like insensitive widgets + (but don't actually make it be insensitive, since we + still want to be able to click on it.) + */ + GtkRcStyle *rc_style; + GdkColor fg, bg; + + gtk_widget_realize (GTK_WIDGET (line_label)); + + fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE]; + bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE]; + + rc_style = gtk_rc_style_new (); + rc_style->fg[GTK_STATE_NORMAL] = fg; + rc_style->bg[GTK_STATE_NORMAL] = bg; + rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG; + + gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style); + gtk_rc_style_unref (rc_style); + } + } + + gtk_signal_connect (GTK_OBJECT (list), "select_child", + GTK_SIGNAL_FUNC (list_select_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (list), "unselect_child", + GTK_SIGNAL_FUNC (list_unselect_cb), + (gpointer) s); +#endif /* !HAVE_GTK2 */ +} + +static void +update_list_sensitivity (state *s) +{ + saver_preferences *p = &s->prefs; + Bool sensitive = (p->mode == RANDOM_HACKS || p->mode == ONE_HACK); + Bool checkable = (p->mode == RANDOM_HACKS); + Bool blankable = (p->mode != DONT_BLANK); + +#ifndef HAVE_GTK2 + GtkWidget *head = name_to_widget (s, "col_head_hbox"); + GtkWidget *use = name_to_widget (s, "use_col_frame"); +#endif /* HAVE_GTK2 */ + GtkWidget *scroller = name_to_widget (s, "scroller"); + GtkWidget *buttons = name_to_widget (s, "next_prev_hbox"); + GtkWidget *blanker = name_to_widget (s, "blanking_table"); + +#ifdef HAVE_GTK2 + GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED); +#else /* !HAVE_GTK2 */ + GtkList *list = GTK_LIST (name_to_widget (s, "list")); + GList *kids = gtk_container_children (GTK_CONTAINER (list)); + + gtk_widget_set_sensitive (GTK_WIDGET (head), sensitive); +#endif /* !HAVE_GTK2 */ + gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (buttons), sensitive); + + gtk_widget_set_sensitive (GTK_WIDGET (blanker), blankable); + +#ifdef HAVE_GTK2 + gtk_tree_view_column_set_visible (use, checkable); +#else /* !HAVE_GTK2 */ + if (checkable) + gtk_widget_show (use); /* the "Use" column header */ + else + gtk_widget_hide (use); + + while (kids) + { + GtkBin *line = GTK_BIN (kids->data); + GtkContainer *line_hbox = GTK_CONTAINER (line->child); + GtkWidget *line_check = + GTK_WIDGET (gtk_container_children (line_hbox)->data); + + if (checkable) + gtk_widget_show (line_check); + else + gtk_widget_hide (line_check); + + kids = kids->next; + } +#endif /* !HAVE_GTK2 */ +} + + +static void +populate_prefs_page (state *s) +{ + saver_preferences *p = &s->prefs; + + Bool can_lock_p = True; + + /* Disable all the "lock" controls if locking support was not provided + at compile-time, or if running on MacOS. */ +# if defined(NO_LOCKING) || defined(__APPLE__) + can_lock_p = False; +# endif + + + /* The file supports timeouts of less than a minute, but the GUI does + not, so throttle the values to be at least one minute (since "0" is + a bad rounding choice...) + */ +# define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000 + THROTTLE (timeout); + THROTTLE (cycle); + /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */ +# undef THROTTLE + +# define FMT_MINUTES(NAME,N) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000)) + +# define FMT_SECONDS(NAME,N) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000)) + + FMT_MINUTES ("timeout_spinbutton", p->timeout); + FMT_MINUTES ("cycle_spinbutton", p->cycle); + FMT_MINUTES ("lock_spinbutton", p->lock_timeout); + FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby); + FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend); + FMT_MINUTES ("dpms_off_spinbutton", p->dpms_off); + FMT_SECONDS ("fade_spinbutton", p->fade_seconds); + +# undef FMT_MINUTES +# undef FMT_SECONDS + +# define TOGGLE_ACTIVE(NAME,ACTIVEP) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\ + (ACTIVEP)) + + TOGGLE_ACTIVE ("lock_button", p->lock_p); + TOGGLE_ACTIVE ("verbose_button", p->verbose_p); + TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p); + TOGGLE_ACTIVE ("splash_button", p->splash_p); + TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p); + TOGGLE_ACTIVE ("grab_desk_button", p->grab_desktop_p); + TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p); + TOGGLE_ACTIVE ("grab_image_button", p->random_image_p); + TOGGLE_ACTIVE ("install_button", p->install_cmap_p); + TOGGLE_ACTIVE ("fade_button", p->fade_p); + TOGGLE_ACTIVE ("unfade_button", p->unfade_p); + +# undef TOGGLE_ACTIVE + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), + (p->image_directory ? p->image_directory : "")); + gtk_widget_set_sensitive (name_to_widget (s, "image_text"), + p->random_image_p); + gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"), + p->random_image_p); + + /* Map the `saver_mode' enum to mode menu to values. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + + int i; + for (i = 0; i < countof(mode_menu_order); i++) + if (mode_menu_order[i] == p->mode) + break; + gtk_option_menu_set_history (opt, i); + update_list_sensitivity (s); + } + + { + Bool found_any_writable_cells = False; + Bool dpms_supported = False; + + Display *dpy = GDK_DISPLAY(); + int nscreens = ScreenCount(dpy); + int i; + for (i = 0; i < nscreens; i++) + { + Screen *s = ScreenOfDisplay (dpy, i); + if (has_writable_cells (s, DefaultVisualOfScreen (s))) + { + found_any_writable_cells = True; + break; + } + } + +#ifdef HAVE_XF86VMODE_GAMMA + found_any_writable_cells = True; /* if we can gamma fade, go for it */ +#endif + +#ifdef HAVE_DPMS_EXTENSION + { + int op = 0, event = 0, error = 0; + if (XQueryExtension (dpy, "DPMS", &op, &event, &error)) + dpms_supported = True; + } +#endif /* HAVE_DPMS_EXTENSION */ + + +# define SENSITIZE(NAME,SENSITIVEP) \ + gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP)) + + /* Blanking and Locking + */ + SENSITIZE ("lock_button", can_lock_p); + SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p); + SENSITIZE ("lock_mlabel", can_lock_p && p->lock_p); + + /* DPMS + */ + SENSITIZE ("dpms_frame", dpms_supported); + SENSITIZE ("dpms_button", dpms_supported); + SENSITIZE ("dpms_standby_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_standby_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_label", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_mlabel", dpms_supported && p->dpms_enabled_p); + SENSITIZE ("dpms_off_spinbutton", dpms_supported && p->dpms_enabled_p); + + /* Colormaps + */ + SENSITIZE ("cmap_frame", found_any_writable_cells); + SENSITIZE ("install_button", found_any_writable_cells); + SENSITIZE ("fade_button", found_any_writable_cells); + SENSITIZE ("unfade_button", found_any_writable_cells); + + SENSITIZE ("fade_label", (found_any_writable_cells && + (p->fade_p || p->unfade_p))); + SENSITIZE ("fade_spinbutton", (found_any_writable_cells && + (p->fade_p || p->unfade_p))); + +# undef SENSITIZE + } +} + + +static void +populate_popup_window (state *s) +{ + GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc")); + char *doc_string = 0; + + /* #### not in Gtk 1.2 + gtk_label_set_selectable (doc); + */ + +# ifdef HAVE_XML + if (s->cdata) + { + free_conf_data (s->cdata); + s->cdata = 0; + } + + { + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); + int hack_number = (list_elt >= 0 && list_elt < s->list_count + ? s->list_elt_to_hack_number[list_elt] + : -1); + screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0); + if (hack) + { + GtkWidget *parent = name_to_widget (s, "settings_vbox"); + GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); + const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd)); + s->cdata = load_configurator (cmd_line, s->debug_p); + if (s->cdata && s->cdata->widget) + gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, + TRUE, TRUE, 0); + } + } + + doc_string = (s->cdata + ? s->cdata->description + : 0); +# else /* !HAVE_XML */ + doc_string = _("Descriptions not available: no XML support compiled in."); +# endif /* !HAVE_XML */ + + gtk_label_set_text (doc, (doc_string + ? _(doc_string) + : _("No description available."))); +} + + +static void +sensitize_demo_widgets (state *s, Bool sensitive_p) +{ + const char *names1[] = { "demo", "settings" }; + const char *names2[] = { "cmd_label", "cmd_text", "manual", + "visual", "visual_combo" }; + int i; + for (i = 0; i < countof(names1); i++) + { + GtkWidget *w = name_to_widget (s, names1[i]); + gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + } + for (i = 0; i < countof(names2); i++) + { + GtkWidget *w = name_to_widget (s, names2[i]); + gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + } +} + + +/* Even though we've given these text fields a maximum number of characters, + their default size is still about 30 characters wide -- so measure out + a string in their font, and resize them to just fit that. + */ +static void +fix_text_entry_sizes (state *s) +{ + GtkWidget *w; + +# if 0 /* appears no longer necessary with Gtk 1.2.10 */ + const char * const spinbuttons[] = { + "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton", + "dpms_standby_spinbutton", "dpms_suspend_spinbutton", + "dpms_off_spinbutton", + "-fade_spinbutton" }; + int i; + int width = 0; + + for (i = 0; i < countof(spinbuttons); i++) + { + const char *n = spinbuttons[i]; + int cols = 4; + while (*n == '-') n++, cols--; + w = GTK_WIDGET (name_to_widget (s, n)); + width = gdk_text_width (w->style->font, "MMMMMMMM", cols); + gtk_widget_set_usize (w, width, -2); + } + + /* Now fix the width of the combo box. + */ + w = GTK_WIDGET (name_to_widget (s, "visual_combo")); + w = GTK_COMBO (w)->entry; + width = gdk_string_width (w->style->font, "PseudoColor___"); + gtk_widget_set_usize (w, width, -2); + + /* Now fix the width of the file entry text. + */ + w = GTK_WIDGET (name_to_widget (s, "image_text")); + width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm"); + gtk_widget_set_usize (w, width, -2); + + /* Now fix the width of the command line text. + */ + w = GTK_WIDGET (name_to_widget (s, "cmd_text")); + width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm"); + gtk_widget_set_usize (w, width, -2); + +# endif /* 0 */ + + /* Now fix the height of the list widget: + make it default to being around 10 text-lines high instead of 4. + */ + w = GTK_WIDGET (name_to_widget (s, "list")); + { + int lines = 10; + int height; + int leading = 3; /* approximate is ok... */ + int border = 2; + +#ifdef HAVE_GTK2 + PangoFontMetrics *pain = + pango_context_get_metrics (gtk_widget_get_pango_context (w), + w->style->font_desc, + gtk_get_default_language ()); + height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) + + pango_font_metrics_get_descent (pain)); +#else /* !HAVE_GTK2 */ + height = w->style->font->ascent + w->style->font->descent; +#endif /* !HAVE_GTK2 */ + + height += leading; + height *= lines; + height += border * 2; + w = GTK_WIDGET (name_to_widget (s, "scroller")); + gtk_widget_set_usize (w, -2, height); + } +} + + +#ifndef HAVE_GTK2 + +/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...) + */ + +static char *up_arrow_xpm[] = { + "15 15 4 1", + " c None", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " @ ", + " @ ", + " -+@ ", + " -+@ ", + " -+++@ ", + " -+++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++++++@ ", + " @@@@@@@@@@@@@ ", + " ", + + /* Need these here because gdk_pixmap_create_from_xpm_d() walks off + the end of the array (Gtk 1.2.5.) */ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" +}; + +static char *down_arrow_xpm[] = { + "15 15 4 1", + " c None", + "- c #FFFFFF", + "+ c #D6D6D6", + "@ c #000000", + + " ", + " ------------- ", + " -+++++++++++@ ", + " -+++++++++@ ", + " -+++++++++@ ", + " -+++++++@ ", + " -+++++++@ ", + " -+++++@ ", + " -+++++@ ", + " -+++@ ", + " -+++@ ", + " -+@ ", + " -+@ ", + " @ ", + " @ ", + + /* Need these here because gdk_pixmap_create_from_xpm_d() walks off + the end of the array (Gtk 1.2.5.) */ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" }; -static char *down_arrow_xpm[] = { - "15 15 4 1", - " c None", - "- c #FFFFFF", - "+ c #D6D6D6", - "@ c #000000", +static void +pixmapify_button (state *s, int down_p) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkWidget *pixmapwid; + GtkStyle *style; + GtkWidget *w; + + w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev"))); + style = gtk_widget_get_style (w); + mask = 0; + pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask, + &style->bg[GTK_STATE_NORMAL], + (down_p + ? (gchar **) down_arrow_xpm + : (gchar **) up_arrow_xpm)); + pixmapwid = gtk_pixmap_new (pixmap, mask); + gtk_widget_show (pixmapwid); + gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child); + gtk_container_add (GTK_CONTAINER (w), pixmapwid); +} + +static void +map_next_button_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + pixmapify_button (s, 1); +} + +static void +map_prev_button_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + pixmapify_button (s, 0); +} +#endif /* !HAVE_GTK2 */ + + +/* Work around a Gtk bug that causes label widgets to wrap text too early. + */ + +static void +you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label, + GtkAllocation *allocation, + void *foo) +{ + GtkRequisition req; + GtkWidgetAuxInfo *aux_info; + + aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info"); + + aux_info->width = allocation->width; + aux_info->height = -2; + aux_info->x = -1; + aux_info->y = -1; + + gtk_widget_size_request (label, &req); +} + + +/* Feel the love. Thanks to Nat Friedman for finding this workaround. + */ +static void +eschew_gtk_lossage (GtkLabel *label) +{ + GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1); + aux_info->width = GTK_WIDGET (label)->allocation.width; + aux_info->height = -2; + aux_info->x = -1; + aux_info->y = -1; + + gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info); + + gtk_signal_connect (GTK_OBJECT (label), "size_allocate", + GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake), + 0); + + gtk_widget_set_usize (GTK_WIDGET (label), -2, -2); + + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + + +static void +populate_demo_window (state *s, int list_elt) +{ + saver_preferences *p = &s->prefs; + screenhack *hack; + char *pretty_name; + GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame")); + GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "doc_frame")); + GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); + GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo")); + GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list")); + + if (p->mode == BLANK_ONLY) + { + hack = 0; + pretty_name = strdup (_("Blank Screen")); + schedule_preview (s, 0); + } + else if (p->mode == DONT_BLANK) + { + hack = 0; + pretty_name = strdup (_("Screen Saver Disabled")); + schedule_preview (s, 0); + } + else + { + int hack_number = (list_elt >= 0 && list_elt < s->list_count + ? s->list_elt_to_hack_number[list_elt] + : -1); + hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0); + + pretty_name = (hack + ? (hack->name + ? strdup (hack->name) + : make_hack_name (hack->command)) + : 0); + + if (hack) + schedule_preview (s, hack->command); + else + schedule_preview (s, 0); + } + + if (!pretty_name) + pretty_name = strdup (_("Preview")); + + gtk_frame_set_label (frame1, _(pretty_name)); + gtk_frame_set_label (frame2, _(pretty_name)); + + gtk_entry_set_text (cmd, (hack ? hack->command : "")); + gtk_entry_set_position (cmd, 0); + + { + char title[255]; + sprintf (title, _("%s: %.100s Settings"), + progclass, (pretty_name ? pretty_name : "???")); + gtk_window_set_title (GTK_WINDOW (s->popup_widget), title); + } + + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), + (hack + ? (hack->visual && *hack->visual + ? hack->visual + : _("Any")) + : "")); + + sensitize_demo_widgets (s, (hack ? True : False)); + + if (pretty_name) free (pretty_name); + + ensure_selected_item_visible (list); + + s->_selected_list_element = list_elt; +} + + +static void +widget_deleter (GtkWidget *widget, gpointer data) +{ + /* #### Well, I want to destroy these widgets, but if I do that, they get + referenced again, and eventually I get a SEGV. So instead of + destroying them, I'll just hide them, and leak a bunch of memory + every time the disk file changes. Go go go Gtk! + + #### Ok, that's a lie, I get a crash even if I just hide the widget + and don't ever delete it. Fuck! + */ +#if 0 + gtk_widget_destroy (widget); +#else + gtk_widget_hide (widget); +#endif +} + + +static char **sort_hack_cmp_names_kludge; +static int +sort_hack_cmp (const void *a, const void *b) +{ + if (a == b) + return 0; + else + { + int aa = *(int *) a; + int bb = *(int *) b; + const char last[] = "\377\377\377\377\377\377\377\377\377\377\377"; + return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]), + (bb < 0 ? last : sort_hack_cmp_names_kludge[bb])); + } +} + + +static void +initialize_sort_map (state *s) +{ + saver_preferences *p = &s->prefs; + int i, j; + + if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number); + if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt); + if (s->hacks_available_p) free (s->hacks_available_p); - " ------------- ", - " -+++++++++++@ ", - " -+++++++++@ ", - " -+++++++++@ ", - " -+++++++@ ", - " -+++++++@ ", - " -+++++@ ", - " -+++++@ ", - " -+++@ ", - " -+++@ ", - " -+@ ", - " -+@ ", - " @ ", - " @ ", + s->list_elt_to_hack_number = (int *) + calloc (sizeof(int), p->screenhacks_count + 1); + s->hack_number_to_list_elt = (int *) + calloc (sizeof(int), p->screenhacks_count + 1); + s->hacks_available_p = (Bool *) + calloc (sizeof(Bool), p->screenhacks_count + 1); - /* Need these here because gdk_pixmap_create_from_xpm_d() walks off - the end of the array (Gtk 1.2.5.) */ - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" -}; + /* Check which hacks actually exist on $PATH + */ + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + s->hacks_available_p[i] = on_path_p (hack->command); + } + + /* Initialize list->hack table to unsorted mapping, omitting nonexistent + hacks, if desired. + */ + j = 0; + for (i = 0; i < p->screenhacks_count; i++) + { + if (!p->ignore_uninstalled_p || + s->hacks_available_p[i]) + s->list_elt_to_hack_number[j++] = i; + } + s->list_count = j; + + for (; j < p->screenhacks_count; j++) + s->list_elt_to_hack_number[j] = -1; + + + /* Generate list of sortable names (once) + */ + sort_hack_cmp_names_kludge = (char **) + calloc (sizeof(char *), p->screenhacks_count); + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + char *name = (hack->name && *hack->name + ? strdup (hack->name) + : make_hack_name (hack->command)); + char *str; + for (str = name; *str; str++) + *str = tolower(*str); + sort_hack_cmp_names_kludge[i] = name; + } + + /* Sort list->hack map alphabetically + */ + qsort (s->list_elt_to_hack_number, + p->screenhacks_count, + sizeof(*s->list_elt_to_hack_number), + sort_hack_cmp); + + /* Free names + */ + for (i = 0; i < p->screenhacks_count; i++) + free (sort_hack_cmp_names_kludge[i]); + free (sort_hack_cmp_names_kludge); + sort_hack_cmp_names_kludge = 0; + + /* Build inverse table */ + for (i = 0; i < p->screenhacks_count; i++) + s->hack_number_to_list_elt[s->list_elt_to_hack_number[i]] = i; +} + + +static int +maybe_reload_init_file (state *s) +{ + saver_preferences *p = &s->prefs; + int status = 0; + + static Bool reentrant_lock = False; + if (reentrant_lock) return 0; + reentrant_lock = True; + + if (init_file_changed_p (p)) + { + const char *f = init_file_name(); + char *b; + int list_elt; + GtkWidget *list; + + if (!f || !*f) return 0; + b = (char *) malloc (strlen(f) + 1024); + sprintf (b, + _("Warning:\n\n" + "file \"%s\" has changed, reloading.\n"), + f); + warning_dialog (s->toplevel_widget, b, False, 100); + free (b); + + load_init_file (p); + initialize_sort_map (s); + + list_elt = selected_list_element (s); + list = name_to_widget (s, "list"); + gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL); + populate_hack_list (s); + force_list_select_item (s, list, list_elt, True); + populate_prefs_page (s); + populate_demo_window (s, list_elt); + ensure_selected_item_visible (list); + + status = 1; + } + + reentrant_lock = False; + return status; +} + + + +/* Making the preview window have the right X visual (so that GL works.) + */ + +static Visual *get_best_gl_visual (state *); + +static GdkVisual * +x_visual_to_gdk_visual (Visual *xv) +{ + GList *gvs = gdk_list_visuals(); + if (!xv) return gdk_visual_get_system(); + for (; gvs; gvs = gvs->next) + { + GdkVisual *gv = (GdkVisual *) gvs->data; + if (xv == GDK_VISUAL_XVISUAL (gv)) + return gv; + } + fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n", + blurb(), (unsigned long) xv->visualid); + abort(); +} static void -pixmapify_buttons (GtkWidget *toplevel) +clear_preview_window (state *s) { - GdkPixmap *pixmap; - GdkBitmap *mask; - GtkWidget *pixmapwid; - GtkStyle *style; - GtkWidget *w; + GtkWidget *p; + GdkWindow *window; - w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "next")); - style = gtk_widget_get_style (w); - mask = 0; - pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask, - &style->bg[GTK_STATE_NORMAL], - (gchar **) down_arrow_xpm); - pixmapwid = gtk_pixmap_new (pixmap, mask); - gtk_widget_show (pixmapwid); - gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child); - gtk_container_add (GTK_CONTAINER (w), pixmapwid); + if (!s->toplevel_widget) return; /* very early */ + p = name_to_widget (s, "preview"); + window = p->window; - w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "prev")); - style = gtk_widget_get_style (w); - mask = 0; - pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask, - &style->bg[GTK_STATE_NORMAL], - (gchar **) up_arrow_xpm); - pixmapwid = gtk_pixmap_new (pixmap, mask); - gtk_widget_show (pixmapwid); - gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child); - gtk_container_add (GTK_CONTAINER (w), pixmapwid); + if (!window) return; + + /* Flush the widget background down into the window, in case a subproc + has changed it. */ + gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]); + gdk_window_clear (window); + + { + int list_elt = selected_list_element (s); + int hack_number = (list_elt >= 0 + ? s->list_elt_to_hack_number[list_elt] + : -1); + Bool available_p = (hack_number >= 0 + ? s->hacks_available_p [hack_number] + : True); +#ifdef HAVE_GTK2 + GtkWidget *notebook = name_to_widget (s, "preview_notebook"); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), + (s->running_preview_error_p + ? (available_p ? 1 : 2) + : 0)); +#else /* !HAVE_GTK2 */ + if (s->running_preview_error_p) + { + const char * const lines1[] = { N_("No Preview"), N_("Available") }; + const char * const lines2[] = { N_("Not"), N_("Installed") }; + int nlines = countof(lines1); + int lh = p->style->font->ascent + p->style->font->descent; + int y, i; + gint w, h; + + const char * const *lines = (available_p ? lines1 : lines2); + + gdk_window_get_size (window, &w, &h); + y = (h - (lh * nlines)) / 2; + y += p->style->font->ascent; + for (i = 0; i < nlines; i++) + { + int sw = gdk_string_width (p->style->font, _(lines[i])); + int x = (w - sw) / 2; + gdk_draw_string (window, p->style->font, + p->style->fg_gc[GTK_STATE_NORMAL], + x, y, _(lines[i])); + y += lh; + } + } +#endif /* !HAVE_GTK2 */ + } + + gdk_flush (); +} + + +static void +reset_preview_window (state *s) +{ + /* On some systems (most recently, MacOS X) OpenGL programs get confused + when you kill one and re-start another on the same window. So maybe + it's best to just always destroy and recreate the preview window + when changing hacks, instead of always trying to reuse the same one? + */ + GtkWidget *pr = name_to_widget (s, "preview"); + if (GTK_WIDGET_REALIZED (pr)) + { + Window oid = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0); + Window id; + gtk_widget_hide (pr); + gtk_widget_unrealize (pr); + gtk_widget_realize (pr); + gtk_widget_show (pr); + id = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0); + if (s->debug_p) + fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(), + (unsigned int) oid, + (unsigned int) id); + } +} + + +static void +fix_preview_visual (state *s) +{ + GtkWidget *widget = name_to_widget (s, "preview"); + Visual *xvisual = get_best_gl_visual (s); + GdkVisual *visual = x_visual_to_gdk_visual (xvisual); + GdkVisual *dvisual = gdk_visual_get_system(); + GdkColormap *cmap = (visual == dvisual + ? gdk_colormap_get_system () + : gdk_colormap_new (visual, False)); + + if (s->debug_p) + fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(), + (visual == dvisual ? "default" : "non-default"), + (xvisual ? (unsigned long) xvisual->visualid : 0L)); + + if (!GTK_WIDGET_REALIZED (widget) || + gtk_widget_get_visual (widget) != visual) + { + gtk_widget_unrealize (widget); + gtk_widget_set_visual (widget, visual); + gtk_widget_set_colormap (widget, cmap); + gtk_widget_realize (widget); + } + + /* Set the Widget colors to be white-on-black. */ + { + GdkWindow *window = widget->window; + GtkStyle *style = gtk_style_copy (widget->style); + GdkColormap *cmap = gtk_widget_get_colormap (widget); + GdkColor *fg = &style->fg[GTK_STATE_NORMAL]; + GdkColor *bg = &style->bg[GTK_STATE_NORMAL]; + GdkGC *fgc = gdk_gc_new(window); + GdkGC *bgc = gdk_gc_new(window); + if (!gdk_color_white (cmap, fg)) abort(); + if (!gdk_color_black (cmap, bg)) abort(); + gdk_gc_set_foreground (fgc, fg); + gdk_gc_set_background (fgc, bg); + gdk_gc_set_foreground (bgc, bg); + gdk_gc_set_background (bgc, fg); + style->fg_gc[GTK_STATE_NORMAL] = fgc; + style->bg_gc[GTK_STATE_NORMAL] = fgc; + gtk_widget_set_style (widget, style); + + /* For debugging purposes, put a title on the window (so that + it can be easily found in the output of "xwininfo -tree".) + */ + gdk_window_set_title (window, "Preview"); + } + + gtk_widget_show (widget); +} + + +/* Subprocesses + */ + +static char * +subproc_pretty_name (state *s) +{ + if (s->running_preview_cmd) + { + char *ps = strdup (s->running_preview_cmd); + char *ss = strchr (ps, ' '); + if (ss) *ss = 0; + ss = strrchr (ps, '/'); + if (!ss) + ss = ps; + else + { + ss = strdup (ss+1); + free (ps); + } + return ss; + } + else + return strdup ("???"); +} + + +static void +reap_zombies (state *s) +{ + int wait_status = 0; + pid_t pid; + while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0) + { + if (s->debug_p) + { + if (pid == s->running_preview_pid) + { + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), + (unsigned long) pid, ss); + free (ss); + } + else + fprintf (stderr, "%s: pid %lu died\n", blurb(), + (unsigned long) pid); + } + } +} + + +/* Mostly lifted from driver/subprocs.c */ +static Visual * +get_best_gl_visual (state *s) +{ + Display *dpy = GDK_DISPLAY(); + pid_t forked; + int fds [2]; + int in, out; + char buf[1024]; + + char *av[10]; + int ac = 0; + + av[ac++] = "xscreensaver-gl-helper"; + av[ac] = 0; + + if (pipe (fds)) + { + perror ("error creating pipe:"); + return 0; + } + + in = fds [0]; + out = fds [1]; + + switch ((int) (forked = fork ())) + { + case -1: + { + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + exit (1); + } + case 0: + { + int stdout_fd = 1; + + close (in); /* don't need this one */ + close (ConnectionNumber (dpy)); /* close display fd */ + + if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stdout:"); + return 0; + } + + execvp (av[0], av); /* shouldn't return. */ + + if (errno != ENOENT) + { + /* Ignore "no such file or directory" errors, unless verbose. + Issue all other exec errors, though. */ + sprintf (buf, "%s: running %s", blurb(), av[0]); + perror (buf); + } + + /* Note that one must use _exit() instead of exit() in procs forked + off of Gtk programs -- Gtk installs an atexit handler that has a + copy of the X connection (which we've already closed, for safety.) + If one uses exit() instead of _exit(), then one sometimes gets a + spurious "Gdk-ERROR: Fatal IO error on X server" error message. + */ + _exit (1); /* exits fork */ + break; + } + default: + { + int result = 0; + int wait_status = 0; + + FILE *f = fdopen (in, "r"); + unsigned int v = 0; + char c; + + close (out); /* don't need this one */ + + *buf = 0; + fgets (buf, sizeof(buf)-1, f); + fclose (f); + + /* Wait for the child to die. */ + waitpid (-1, &wait_status, 0); + + if (1 == sscanf (buf, "0x%x %c", &v, &c)) + result = (int) v; + + if (result == 0) + { + if (s->debug_p) + fprintf (stderr, "%s: %s did not report a GL visual!\n", + blurb(), av[0]); + return 0; + } + else + { + Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result); + if (s->debug_p) + fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n", + blurb(), av[0], result); + if (!v) abort(); + return v; + } + } + } + + abort(); +} + + +static void +kill_preview_subproc (state *s, Bool reset_p) +{ + s->running_preview_error_p = False; + + reap_zombies (s); + clear_preview_window (s); + + if (s->subproc_check_timer_id) + { + gtk_timeout_remove (s->subproc_check_timer_id); + s->subproc_check_timer_id = 0; + s->subproc_check_countdown = 0; + } + + if (s->running_preview_pid) + { + int status = kill (s->running_preview_pid, SIGTERM); + char *ss = subproc_pretty_name (s); + + if (status < 0) + { + if (errno == ESRCH) + { + if (s->debug_p) + fprintf (stderr, "%s: pid %lu (%s) was already dead.\n", + blurb(), (unsigned long) s->running_preview_pid, ss); + } + else + { + char buf [1024]; + sprintf (buf, "%s: couldn't kill pid %lu (%s)", + blurb(), (unsigned long) s->running_preview_pid, ss); + perror (buf); + } + } + else if (s->debug_p) + fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(), + (unsigned long) s->running_preview_pid, ss); + + free (ss); + s->running_preview_pid = 0; + if (s->running_preview_cmd) free (s->running_preview_cmd); + s->running_preview_cmd = 0; + } + + reap_zombies (s); + + if (reset_p) + { + reset_preview_window (s); + clear_preview_window (s); + } +} + + +/* Immediately and unconditionally launches the given process, + after appending the -window-id option; sets running_preview_pid. + */ +static void +launch_preview_subproc (state *s) +{ + saver_preferences *p = &s->prefs; + Window id; + char *new_cmd = 0; + pid_t forked; + const char *cmd = s->desired_preview_cmd; + + GtkWidget *pr = name_to_widget (s, "preview"); + GdkWindow *window; + + reset_preview_window (s); + + window = pr->window; + + s->running_preview_error_p = False; + + if (s->preview_suppressed_p) + { + kill_preview_subproc (s, False); + goto DONE; + } + + new_cmd = malloc (strlen (cmd) + 40); + + id = (window ? GDK_WINDOW_XWINDOW (window) : 0); + if (id == 0) + { + /* No window id? No command to run. */ + free (new_cmd); + new_cmd = 0; + } + else + { + strcpy (new_cmd, cmd); + sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", + (unsigned int) id); + } + + kill_preview_subproc (s, False); + if (! new_cmd) + { + s->running_preview_error_p = True; + clear_preview_window (s); + goto DONE; + } + + switch ((int) (forked = fork ())) + { + case -1: + { + char buf[255]; + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + s->running_preview_error_p = True; + goto DONE; + break; + } + case 0: + { + close (ConnectionNumber (GDK_DISPLAY())); + + hack_subproc_environment (id, s->debug_p); + + usleep (250000); /* pause for 1/4th second before launching, to give + the previous program time to die and flush its X + buffer, so we don't get leftover turds on the + window. */ + + exec_command (p->shell, new_cmd, p->nice_inferior); + /* Don't bother printing an error message when we are unable to + exec subprocesses; we handle that by polling the pid later. + + Note that one must use _exit() instead of exit() in procs forked + off of Gtk programs -- Gtk installs an atexit handler that has a + copy of the X connection (which we've already closed, for safety.) + If one uses exit() instead of _exit(), then one sometimes gets a + spurious "Gdk-ERROR: Fatal IO error on X server" error message. + */ + _exit (1); /* exits child fork */ + break; + + default: + + if (s->running_preview_cmd) free (s->running_preview_cmd); + s->running_preview_cmd = strdup (s->desired_preview_cmd); + s->running_preview_pid = forked; + + if (s->debug_p) + { + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), + (unsigned long) forked, ss); + free (ss); + } + break; + } + } + + schedule_preview_check (s); + + DONE: + if (new_cmd) free (new_cmd); + new_cmd = 0; +} + + +/* Modify $DISPLAY and $PATH for the benefit of subprocesses. + */ +static void +hack_environment (state *s) +{ + static const char *def_path = +# ifdef DEFAULT_PATH_PREFIX + DEFAULT_PATH_PREFIX; +# else + ""; +# endif + + Display *dpy = GDK_DISPLAY(); + const char *odpy = DisplayString (dpy); + char *ndpy = (char *) malloc(strlen(odpy) + 20); + strcpy (ndpy, "DISPLAY="); + strcat (ndpy, odpy); + if (putenv (ndpy)) + abort (); + + if (s->debug_p) + fprintf (stderr, "%s: %s\n", blurb(), ndpy); + + /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc + 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not. + So we must leak it (and/or the previous setting). Yay. + */ + + if (def_path && *def_path) + { + const char *opath = getenv("PATH"); + char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20); + strcpy (npath, "PATH="); + strcat (npath, def_path); + strcat (npath, ":"); + strcat (npath, opath); + + if (putenv (npath)) + abort (); + /* do not free(npath) -- see above */ + + if (s->debug_p) + fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path); + } } - -/* Work around a Gtk bug that causes label widgets to wrap text too early. - */ - static void -you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label, - GtkAllocation *allocation, - void *foo) +hack_subproc_environment (Window preview_window_id, Bool debug_p) { - GtkRequisition req; - GtkWidgetAuxInfo *aux_info; + /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly + necessary yet, but it will make programs work if we had invoked + them with "-root" and not with "-window-id" -- which, of course, + doesn't happen. + */ + char *nssw = (char *) malloc (40); + sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id); - aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info"); + /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems + any more, right? It's not Posix, but everyone seems to have it. */ + if (putenv (nssw)) + abort (); - aux_info->width = allocation->width; - aux_info->height = -2; - aux_info->x = -1; - aux_info->y = -1; + if (debug_p) + fprintf (stderr, "%s: %s\n", blurb(), nssw); - gtk_widget_size_request (label, &req); + /* do not free(nssw) -- see above */ } -/* Feel the love. Thanks to Nat Friedman for finding this workaround. +/* Called from a timer: + Launches the currently-chosen subprocess, if it's not already running. + If there's a different process running, kills it. */ -static void -eschew_gtk_lossage (GtkWidget *toplevel) +static int +update_subproc_timer (gpointer data) { - GtkWidgetAuxInfo *aux_info; - GtkWidget *label = GTK_WIDGET (name_to_widget (toplevel, "doc")); + state *s = (state *) data; + if (! s->desired_preview_cmd) + kill_preview_subproc (s, True); + else if (!s->running_preview_cmd || + !!strcmp (s->desired_preview_cmd, s->running_preview_cmd)) + launch_preview_subproc (s); + + s->subproc_timer_id = 0; + return FALSE; /* do not re-execute timer */ +} - aux_info = g_new0 (GtkWidgetAuxInfo, 1); - aux_info->width = label->allocation.width; - aux_info->height = -2; - aux_info->x = -1; - aux_info->y = -1; - gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info); +/* Call this when you think you might want a preview process running. + It will set a timer that will actually launch that program a second + from now, if you haven't changed your mind (to avoid double-click + spazzing, etc.) `cmd' may be null meaning "no process". + */ +static void +schedule_preview (state *s, const char *cmd) +{ + int delay = 1000 * 0.5; /* 1/2 second hysteresis */ - gtk_signal_connect (GTK_OBJECT (label), "size_allocate", - you_are_not_a_unique_or_beautiful_snowflake, NULL); + if (s->debug_p) + { + if (cmd) + fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd); + else + fprintf (stderr, "%s: scheduling preview death\n", blurb()); + } + + if (s->desired_preview_cmd) free (s->desired_preview_cmd); + s->desired_preview_cmd = (cmd ? strdup (cmd) : 0); - gtk_widget_queue_resize (label); + if (s->subproc_timer_id) + gtk_timeout_remove (s->subproc_timer_id); + s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s); } -char * -get_hack_blurb (screenhack *hack) +/* Called from a timer: + Checks to see if the subproc that should be running, actually is. + */ +static int +check_subproc_timer (gpointer data) { - char *doc_string; - char *prog_name = strdup (hack->command); - char *pretty_name = (hack->name - ? strdup (hack->name) - : make_pretty_name (hack->command)); - char doc_name[255], doc_class[255]; - char *s, *s2; + state *s = (state *) data; + Bool again_p = True; - for (s = prog_name; *s && !isspace(*s); s++) - ; - *s = 0; - s = strrchr (prog_name, '/'); - if (s) strcpy (prog_name, s+1); - - sprintf (doc_name, "hacks.%s.documentation", pretty_name); - sprintf (doc_class, "hacks.%s.documentation", prog_name); - free (prog_name); - free (pretty_name); - - doc_string = get_string_resource (doc_name, doc_class); - if (doc_string) + if (s->running_preview_error_p || /* already dead */ + s->running_preview_pid <= 0) { - for (s = doc_string; *s; s++) - { - if (*s == '\n') - { - /* skip over whitespace at beginning of line */ - s++; - while (*s && (*s == ' ' || *s == '\t')) - s++; - } - else if (*s == ' ' || *s == '\t') - { - /* compress all other horizontal whitespace. */ - *s = ' '; - s++; - for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++) - ; - if (s2 > s) strcpy (s, s2); - s--; - } - } - - while (*s && isspace (*s)) /* Strip trailing whitespace */ - *(--s) = 0; - - /* Delete whitespace at end of each line. */ - for (; s > doc_string; s--) - if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t')) - { - for (s2 = s-1; - s2 > doc_string && (*s2 == ' ' || *s2 == '\t'); - s2--) - ; - s2++; - if (s2 < s) strcpy (s2, s); - s = s2; - } - - /* Delete leading blank lines. */ - for (s = doc_string; *s == '\n'; s++) - ; - if (s > doc_string) strcpy (doc_string, s); + again_p = False; } else { - static int doc_installed = 0; - if (doc_installed == 0) + int status; + reap_zombies (s); + status = kill (s->running_preview_pid, 0); + if (status < 0 && errno == ESRCH) + s->running_preview_error_p = True; + + if (s->debug_p) { - if (get_boolean_resource ("hacks.documentation.isInstalled", - "hacks.documentation.isInstalled")) - doc_installed = 1; - else - doc_installed = -1; + char *ss = subproc_pretty_name (s); + fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(), + (unsigned long) s->running_preview_pid, ss, + (s->running_preview_error_p ? "dead" : "alive")); + free (ss); } - if (doc_installed < 0) - doc_string = - strdup ("Error:\n\n" - "The documentation strings do not appear to be " - "installed. This is probably because there is " - "an \"XScreenSaver\" app-defaults file installed " - "that is from an older version of the program. " - "To fix this problem, delete that file, or " - "install a current version (either will work.)"); - else - doc_string = strdup (""); + if (s->running_preview_error_p) + { + clear_preview_window (s); + again_p = False; + } } - return doc_string; + /* Otherwise, it's currently alive. We might be checking again, or we + might be satisfied. */ + + if (--s->subproc_check_countdown <= 0) + again_p = False; + + if (again_p) + return TRUE; /* re-execute timer */ + else + { + s->subproc_check_timer_id = 0; + s->subproc_check_countdown = 0; + return FALSE; /* do not re-execute timer */ + } } +/* Call this just after launching a subprocess. + This sets a timer that will, five times a second for two seconds, + check whether the program is still running. The assumption here + is that if the process didn't stay up for more than a couple of + seconds, then either the program doesn't exist, or it doesn't + take a -window-id argument. + */ static void -populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair) -{ - saver_preferences *p = pair->a; - screenhack *hack = (which >= 0 ? p->screenhacks[which] : 0); - GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame")); - GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc")); - GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text")); - GtkToggleButton *enabled = - GTK_TOGGLE_BUTTON (name_to_widget (toplevel, "enabled")); - GtkCombo *vis = GTK_COMBO (name_to_widget (toplevel, "visual_combo")); - - char *pretty_name = (hack - ? (hack->name - ? strdup (hack->name) - : make_pretty_name (hack->command)) - : 0); - char *doc_string = hack ? get_hack_blurb (hack) : 0; - - gtk_frame_set_label (frame, (pretty_name ? pretty_name : "")); - gtk_label_set_text (doc, (doc_string ? doc_string : "")); - gtk_entry_set_text (cmd, (hack ? hack->command : "")); - gtk_entry_set_position (cmd, 0); - gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False)); - gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), - (hack - ? (hack->visual && hack->visual - ? hack->visual - : "Any") - : "")); +schedule_preview_check (state *s) +{ + int seconds = 2; + int ticks = 5; + + if (s->debug_p) + fprintf (stderr, "%s: scheduling check\n", blurb()); + + if (s->subproc_check_timer_id) + gtk_timeout_remove (s->subproc_check_timer_id); + s->subproc_check_timer_id = + gtk_timeout_add (1000 / ticks, + check_subproc_timer, (gpointer) s); + s->subproc_check_countdown = ticks * seconds; +} - gtk_container_resize_children (GTK_CONTAINER (GTK_WIDGET (doc)->parent)); - sensitize_demo_widgets (toplevel, (hack ? True : False)); +static Bool +screen_blanked_p (void) +{ + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *dataP = 0; + Display *dpy = GDK_DISPLAY(); + Bool blanked_p = False; - if (pretty_name) free (pretty_name); - if (doc_string) free (doc_string); + if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + XA_SCREENSAVER_STATUS, + 0, 3, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + { + Atom *data = (Atom *) dataP; + blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK); + } + + if (dataP) XFree (dataP); + + return blanked_p; +} + +/* Wake up every now and then and see if the screen is blanked. + If it is, kill off the small-window demo -- no point in wasting + cycles by running two screensavers at once... + */ +static int +check_blanked_timer (gpointer data) +{ + state *s = (state *) data; + Bool blanked_p = screen_blanked_p (); + if (blanked_p && s->running_preview_pid) + { + if (s->debug_p) + fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb()); + kill_preview_subproc (s, True); + } + + return True; /* re-execute timer */ } + +/* Setting window manager icon + */ +static void +init_icon (GdkWindow *window) +{ + GdkBitmap *mask = 0; + GdkColor transp; + GdkPixmap *pixmap = + gdk_pixmap_create_from_xpm_d (window, &mask, &transp, + (gchar **) logo_50_xpm); + if (pixmap) + gdk_window_set_icon (window, 0, pixmap, mask); +} /* The main demo-mode command loop. @@ -1368,10 +3813,10 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, static void -the_network_is_not_the_computer (GtkWidget *parent) +the_network_is_not_the_computer (state *s) { - Display *dpy = gdk_display; - char *rversion, *ruser, *rhost; + Display *dpy = GDK_DISPLAY(); + char *rversion = 0, *ruser = 0, *rhost = 0; char *luser, *lhost; char *msg = 0; struct passwd *p = getpwuid (getuid ()); @@ -1410,11 +3855,9 @@ the_network_is_not_the_computer (GtkWidget *parent) if (!rversion || !*rversion) { sprintf (msg, - "Warning:\n\n" - "The XScreenSaver daemon doesn't seem to be running\n" - "on display \"%s\". You can launch it by selecting\n" - "`Restart Daemon' from the File menu, or by typing\n" - "\"xscreensaver &\" in a shell.", + _("Warning:\n\n" + "The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%s\". Launch it now?"), d); } else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name)) @@ -1422,7 +3865,7 @@ the_network_is_not_the_computer (GtkWidget *parent) /* Warn that the two processes are running as different users. */ sprintf(msg, - "Warning:\n\n" + _("Warning:\n\n" "%s is running as user \"%s\" on host \"%s\".\n" "But the xscreensaver managing display \"%s\"\n" "is running as user \"%s\" on host \"%s\".\n" @@ -1431,9 +3874,10 @@ the_network_is_not_the_computer (GtkWidget *parent) "the same ~/.xscreensaver file, so %s isn't\n" "going to work right.\n" "\n" - "Either re-run %s as \"%s\", or re-run\n" - "xscreensaver as \"%s\" (which you can do by\n" - "selecting `Restart Daemon' from the File menu.)\n", + "You should either re-run %s as \"%s\", or re-run\n" + "xscreensaver as \"%s\".\n" + "\n" + "Restart the xscreensaver daemon now?\n"), progname, luser, lhost, d, (ruser ? ruser : "???"), (rhost ? rhost : "???"), @@ -1446,7 +3890,7 @@ the_network_is_not_the_computer (GtkWidget *parent) /* Warn that the two processes are running on different hosts. */ sprintf (msg, - "Warning:\n\n" + _("Warning:\n\n" "%s is running as user \"%s\" on host \"%s\".\n" "But the xscreensaver managing display \"%s\"\n" "is running as user \"%s\" on host \"%s\".\n" @@ -1455,8 +3899,7 @@ the_network_is_not_the_computer (GtkWidget *parent) "if they don't see the same ~%s/.xscreensaver file) then\n" "%s won't work right.\n" "\n" - "You can restart the daemon on \"%s\" as \"%s\" by\n" - "selecting `Restart Daemon' from the File menu.)", + "Restart the daemon on \"%s\" as \"%s\" now?\n"), progname, luser, lhost, d, (ruser ? ruser : "???"), (rhost ? rhost : "???"), @@ -1464,24 +3907,29 @@ the_network_is_not_the_computer (GtkWidget *parent) progname, lhost, luser); } - else if (!!strcmp (rversion, short_version)) + else if (!!strcmp (rversion, s->short_version)) { /* Warn that the version numbers don't match. */ sprintf (msg, - "Warning:\n\n" + _("Warning:\n\n" "This is %s version %s.\n" "But the xscreensaver managing display \"%s\"\n" - "is version %s. This could cause problems.", - progname, short_version, + "is version %s. This could cause problems.\n" + "\n" + "Restart the xscreensaver daemon now?\n"), + progname, s->short_version, d, rversion); } if (*msg) - warning_dialog (parent, msg, 1); + warning_dialog (s->toplevel_widget, msg, True, 1); + if (rversion) free (rversion); + if (ruser) free (ruser); + if (rhost) free (rhost); free (msg); } @@ -1492,11 +3940,11 @@ the_network_is_not_the_computer (GtkWidget *parent) static int demo_ehandler (Display *dpy, XErrorEvent *error) { - fprintf (stderr, "\nX error in %s:\n", progname); - if (XmuPrintDefaultErrorMessage (dpy, error, stderr)) - exit (-1); - else - fprintf (stderr, " (nonfatal.)\n"); + state *s = global_state_kludge; /* I hate C so much... */ + fprintf (stderr, "\nX error in %s:\n", blurb()); + XmuPrintDefaultErrorMessage (dpy, error, stderr); + kill_preview_subproc (s, False); + exit (-1); return 0; } @@ -1518,7 +3966,8 @@ g_log_handler (const gchar *log_domain, GLogLevelFlags log_level, if (strstr (message, "unknown window")) return; - fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain, + fprintf (stderr, "%s: %s-%s: %s%s", blurb(), + (log_domain ? log_domain : progclass), (log_level == G_LOG_LEVEL_ERROR ? "error" : log_level == G_LOG_LEVEL_CRITICAL ? "critical" : log_level == G_LOG_LEVEL_WARNING ? "warning" : @@ -1531,43 +3980,160 @@ g_log_handler (const gchar *log_domain, GLogLevelFlags log_level, } +#ifdef __GNUC__ + __extension__ /* shut up about "string length is greater than the length + ISO C89 compilers are required to support" when including + the .ad file... */ +#endif + static char *defaults[] = { #include "XScreenSaver_ad.h" 0 }; +#if 0 +#ifdef HAVE_CRAPPLET +static struct poptOption crapplet_options[] = { + {NULL, '\0', 0, NULL, 0} +}; +#endif /* HAVE_CRAPPLET */ +#endif /* 0 */ + +const char *usage = "[--display dpy] [--prefs]" +# ifdef HAVE_CRAPPLET + " [--crapplet]" +# endif + "\n\t\t [--debug] [--sync] [--no-xshm] [--configdir dir]"; + +static void +map_popup_window_cb (GtkWidget *w, gpointer user_data) +{ + state *s = (state *) user_data; + Boolean oi = s->initializing_p; + GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc")); + s->initializing_p = True; + eschew_gtk_lossage (label); + s->initializing_p = oi; +} + + +#if 0 +static void +print_widget_tree (GtkWidget *w, int depth) +{ + int i; + for (i = 0; i < depth; i++) + fprintf (stderr, " "); + fprintf (stderr, "%s\n", gtk_widget_get_name (w)); + + if (GTK_IS_LIST (w)) + { + for (i = 0; i < depth+1; i++) + fprintf (stderr, " "); + fprintf (stderr, "...list kids...\n"); + } + else if (GTK_IS_CONTAINER (w)) + { + GList *kids = gtk_container_children (GTK_CONTAINER (w)); + while (kids) + { + print_widget_tree (GTK_WIDGET (kids->data), depth+1); + kids = kids->next; + } + } +} +#endif /* 0 */ + +static int +delayed_scroll_kludge (gpointer data) +{ + state *s = (state *) data; + GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list")); + ensure_selected_item_visible (w); + + /* Oh, this is just fucking lovely, too. */ + w = GTK_WIDGET (name_to_widget (s, "preview")); + gtk_widget_hide (w); + gtk_widget_show (w); + + return FALSE; /* do not re-execute timer */ +} + +#ifdef HAVE_GTK2 + +GtkWidget * +create_xscreensaver_demo (void) +{ + GtkWidget *nb; + + nb = name_to_widget (global_state_kludge, "preview_notebook"); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE); + + return name_to_widget (global_state_kludge, "xscreensaver_demo"); +} + +GtkWidget * +create_xscreensaver_settings_dialog (void) +{ + GtkWidget *w, *box; + + box = name_to_widget (global_state_kludge, "dialog_action_area"); + + w = name_to_widget (global_state_kludge, "adv_button"); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + + w = name_to_widget (global_state_kludge, "std_button"); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + + return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog"); +} + +#endif /* HAVE_GTK2 */ + int main (int argc, char **argv) { XtAppContext app; - prefs_pair Pair, *pair; - saver_preferences P, P2, *p, *p2; + state S, *s; + saver_preferences *p; Bool prefs = False; int i; Display *dpy; Widget toplevel_shell; - GtkWidget *gtk_window; char *real_progname = argv[0]; - char *s; + char *window_title; + char *geom = 0; + Bool crapplet_p = False; + char *str; - s = strrchr (real_progname, '/'); - if (s) real_progname = s+1; +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); - p = &P; - p2 = &P2; - pair = &Pair; - pair->a = p; - pair->b = p2; - memset (p, 0, sizeof (*p)); - memset (p2, 0, sizeof (*p2)); +# ifdef HAVE_GTK2 + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +# else /* !HAVE_GTK2 */ + if (!setlocale (LC_ALL, "")) + fprintf (stderr, "%s: locale not supported by C library\n", real_progname); +# endif /* !HAVE_GTK2 */ - global_prefs_pair = pair; /* I hate C so much... */ +#endif /* ENABLE_NLS */ + + str = strrchr (real_progname, '/'); + if (str) real_progname = str+1; + + s = &S; + memset (s, 0, sizeof(*s)); + s->initializing_p = True; + p = &s->prefs; + + global_state_kludge = s; /* I hate C so much... */ progname = real_progname; - short_version = (char *) malloc (5); - memcpy (short_version, screensaver_id + 17, 4); - short_version [4] = 0; + s->short_version = (char *) malloc (5); + memcpy (s->short_version, screensaver_id + 17, 4); + s->short_version [4] = 0; /* Register our error message logger for every ``log domain'' known. @@ -1575,12 +4141,22 @@ main (int argc, char **argv) for all of the domains that seem to be in use. */ { - const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule", - "GThread", "Gnome", "GnomeUI", 0 }; - for (i = 0; domains[i]; i++) + const char * const domains[] = { 0, + "Gtk", "Gdk", "GLib", "GModule", + "GThread", "Gnome", "GnomeUI" }; + for (i = 0; i < countof(domains); i++) g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0); } +#ifdef DEFAULT_ICONDIR /* from -D on compile line */ +# ifndef HAVE_GTK2 + { + const char *dir = DEFAULT_ICONDIR; + if (*dir) add_pixmap_directory (dir); + } +# endif /* !HAVE_GTK2 */ +#endif /* DEFAULT_ICONDIR */ + /* This is gross, but Gtk understands --display and not -display... */ for (i = 1; i < argc; i++) @@ -1588,9 +4164,165 @@ main (int argc, char **argv) !strncmp(argv[i], "-display", strlen(argv[i]))) argv[i] = "--display"; + + /* We need to parse this arg really early... Sigh. */ + for (i = 1; i < argc; i++) + { + if (argv[i] && + (!strcmp(argv[i], "--crapplet") || + !strcmp(argv[i], "--capplet"))) + { +# if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2) + int j; + crapplet_p = True; + for (j = i; j < argc; j++) /* remove it from the list */ + argv[j] = argv[j+1]; + argc--; +# else /* !HAVE_CRAPPLET && !HAVE_GTK2 */ + fprintf (stderr, "%s: not compiled with --crapplet support\n", + real_progname); + fprintf (stderr, "%s: %s\n", real_progname, usage); + exit (1); +# endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */ + } + else if (argv[i] && + (!strcmp(argv[i], "--debug") || + !strcmp(argv[i], "-debug") || + !strcmp(argv[i], "-d"))) + { + int j; + s->debug_p = True; + for (j = i; j < argc; j++) /* remove it from the list */ + argv[j] = argv[j+1]; + argc--; + i--; + } + else if (argv[i] && + argc > i+1 && + *argv[i+1] && + (!strcmp(argv[i], "-geometry") || + !strcmp(argv[i], "-geom") || + !strcmp(argv[i], "-geo") || + !strcmp(argv[i], "-g"))) + { + int j; + geom = argv[i+1]; + for (j = i; j < argc; j++) /* remove them from the list */ + argv[j] = argv[j+2]; + argc -= 2; + i -= 2; + } + else if (argv[i] && + argc > i+1 && + *argv[i+1] && + (!strcmp(argv[i], "--configdir"))) + { + int j; + struct stat st; + hack_configuration_path = argv[i+1]; + for (j = i; j < argc; j++) /* remove them from the list */ + argv[j] = argv[j+2]; + argc -= 2; + i -= 2; + + if (0 != stat (hack_configuration_path, &st)) + { + char buf[255]; + sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path); + perror (buf); + exit (1); + } + else if (!S_ISDIR (st.st_mode)) + { + fprintf (stderr, "%s: not a directory: %s\n", + blurb(), hack_configuration_path); + exit (1); + } + } + } + + + if (s->debug_p) + fprintf (stderr, "%s: using config directory \"%s\"\n", + progname, hack_configuration_path); + + /* Let Gtk open the X connection, then initialize Xt to use that - same connection. Doctor Frankenstein would be proud. */ - gtk_init (&argc, &argv); + same connection. Doctor Frankenstein would be proud. + */ +# ifdef HAVE_CRAPPLET + if (crapplet_p) + { + GnomeClient *client; + GnomeClientFlags flags = 0; + + int init_results = gnome_capplet_init ("screensaver-properties", + s->short_version, + argc, argv, NULL, 0, NULL); + /* init_results is: + 0 upon successful initialization; + 1 if --init-session-settings was passed on the cmdline; + 2 if --ignore was passed on the cmdline; + -1 on error. + + So the 1 signifies just to init the settings, and quit, basically. + (Meaning launch the xscreensaver daemon.) + */ + + if (init_results < 0) + { +# if 0 + g_error ("An initialization error occurred while " + "starting xscreensaver-capplet.\n"); +# else /* !0 */ + fprintf (stderr, "%s: gnome_capplet_init failed: %d\n", + real_progname, init_results); + exit (1); +# endif /* !0 */ + } + + client = gnome_master_client (); + + if (client) + flags = gnome_client_get_flags (client); + + if (flags & GNOME_CLIENT_IS_CONNECTED) + { + int token = + gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES", + gnome_client_get_id (client)); + if (token) + { + char *session_args[20]; + int i = 0; + session_args[i++] = real_progname; + session_args[i++] = "--capplet"; + session_args[i++] = "--init-session-settings"; + session_args[i] = 0; + gnome_client_set_priority (client, 20); + gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY); + gnome_client_set_restart_command (client, i, session_args); + } + else + { + gnome_client_set_restart_style (client, GNOME_RESTART_NEVER); + } + + gnome_client_flush (client); + } + + if (init_results == 1) + { + system ("xscreensaver -nosplash &"); + return 0; + } + + } + else +# endif /* HAVE_CRAPPLET */ + { + gtk_init (&argc, &argv); + } /* We must read exactly the same resources as xscreensaver. @@ -1606,7 +4338,7 @@ main (int argc, char **argv) */ XtToolkitInitialize (); app = XtCreateApplicationContext (); - dpy = gdk_display; + dpy = GDK_DISPLAY(); XtAppSetFallbackResources (app, defaults); XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv); toplevel_shell = XtAppCreateShell (progname, progclass, @@ -1618,22 +4350,29 @@ main (int argc, char **argv) XtGetApplicationNameAndClass (dpy, &progname, &progclass); XSetErrorHandler (demo_ehandler); + /* Let's just ignore these. They seem to confuse Irix Gtk... */ + signal (SIGPIPE, SIG_IGN); /* After doing Xt-style command-line processing, complain about any unrecognized command-line arguments. */ for (i = 1; i < argc; i++) { - char *s = argv[i]; - if (s[0] == '-' && s[1] == '-') - s++; - if (!strcmp (s, "-prefs")) + char *str = argv[i]; + if (str[0] == '-' && str[1] == '-') + str++; + if (!strcmp (str, "-prefs")) prefs = True; + else if (crapplet_p) + /* There are lots of random args that we don't care about when we're + started as a crapplet, so just ignore unknown args in that case. */ + ; else { - fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", - real_progname); - exit (1); + fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, + argv[i]); + fprintf (stderr, "%s: %s\n", real_progname, usage); + exit (1); } } @@ -1643,8 +4382,11 @@ main (int argc, char **argv) was in argv[0]. */ p->db = db; + + hack_environment (s); /* must be before initialize_sort_map() */ + load_init_file (p); - *p2 = *p; + initialize_sort_map (s); /* Now that Xt has been initialized, and the resources have been read, we can set our `progname' variable to something more in line with @@ -1684,11 +4426,14 @@ main (int argc, char **argv) /* Create the window and all its widgets. */ - gtk_window = create_xscreensaver_demo (); + s->base_widget = create_xscreensaver_demo (); + s->popup_widget = create_xscreensaver_settings_dialog (); + s->toplevel_widget = s->base_widget; + - /* Set the window's title. */ + /* Set the main window's title. */ { - char title[255]; + char *base_title = _("Screensaver Preferences"); char *v = (char *) strdup(strchr(screensaver_id, ' ')); char *s1, *s2, *s3, *s4; s1 = (char *) strchr(v, ' '); s1++; @@ -1697,38 +4442,172 @@ main (int argc, char **argv) s4 = (char *) strchr(s3, ')'); *s2 = 0; *s4 = 0; - sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3); - gtk_window_set_title (GTK_WINDOW (gtk_window), title); + + window_title = (char *) malloc (strlen (base_title) + + strlen (progclass) + + strlen (s1) + strlen (s3) + + 100); + sprintf (window_title, "%s (%s %s, %s)", base_title, progclass, s1, s3); + gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title); + gtk_window_set_title (GTK_WINDOW (s->popup_widget), window_title); free (v); } + /* Adjust the (invisible) notebooks on the popup dialog... */ + { + GtkNotebook *notebook = + GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button")); + int page = 0; + +# ifdef HAVE_XML + gtk_widget_hide (std); +# else /* !HAVE_XML */ + /* Make the advanced page be the only one available. */ + gtk_widget_set_sensitive (std, False); + std = GTK_WIDGET (name_to_widget (s, "adv_button")); + gtk_widget_hide (std); + page = 1; +# endif /* !HAVE_XML */ + + gtk_notebook_set_page (notebook, page); + gtk_notebook_set_show_tabs (notebook, False); + } + /* Various other widget initializations... */ - gtk_signal_connect (GTK_OBJECT (gtk_window), "delete_event", - GTK_SIGNAL_FUNC (wm_close_cb), NULL); - - populate_hack_list (gtk_window, pair); - populate_prefs_page (gtk_window, pair); - sensitize_demo_widgets (gtk_window, False); - fix_text_entry_sizes (gtk_window); - scroll_to_current_hack (gtk_window, pair); - gtk_widget_show (gtk_window); + gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event", + GTK_SIGNAL_FUNC (wm_toplevel_close_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event", + GTK_SIGNAL_FUNC (wm_popup_close_cb), + (gpointer) s); + + populate_hack_list (s); + populate_prefs_page (s); + sensitize_demo_widgets (s, False); + fix_text_entry_sizes (s); + scroll_to_current_hack (s); + + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")), + "map", GTK_SIGNAL_FUNC(map_popup_window_cb), + (gpointer) s); + +#ifndef HAVE_GTK2 + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")), + "map", GTK_SIGNAL_FUNC(map_prev_button_cb), + (gpointer) s); + gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")), + "map", GTK_SIGNAL_FUNC(map_next_button_cb), + (gpointer) s); +#endif /* !HAVE_GTK2 */ + + /* Hook up callbacks to the items on the mode menu. */ + { + GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); + GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt)); + GList *kids = gtk_container_children (GTK_CONTAINER (menu)); + for (; kids; kids = kids->next) + gtk_signal_connect (GTK_OBJECT (kids->data), "activate", + GTK_SIGNAL_FUNC (mode_menu_item_cb), + (gpointer) s); + } - /* The next three calls must come after gtk_widget_show(). */ - pixmapify_buttons (gtk_window); - eschew_gtk_lossage (gtk_window); - ensure_selected_item_visible (GTK_WIDGET(name_to_widget(gtk_window,"list"))); /* Handle the -prefs command-line argument. */ if (prefs) { GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (gtk_window, "notebook")); + GTK_NOTEBOOK (name_to_widget (s, "notebook")); gtk_notebook_set_page (notebook, 1); } +# ifdef HAVE_CRAPPLET + if (crapplet_p) + { + GtkWidget *capplet; + GtkWidget *outer_vbox; + + gtk_widget_hide (s->toplevel_widget); + + capplet = capplet_widget_new (); + + /* Make there be a "Close" button instead of "OK" and "Cancel" */ +# ifdef HAVE_CRAPPLET_IMMEDIATE + capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet)); +# endif /* HAVE_CRAPPLET_IMMEDIATE */ + /* In crapplet-mode, take off the menubar. */ + gtk_widget_hide (name_to_widget (s, "menubar")); + + /* Reparent our top-level container to be a child of the capplet + window. + */ + outer_vbox = GTK_BIN (s->toplevel_widget)->child; + gtk_widget_ref (outer_vbox); + gtk_container_remove (GTK_CONTAINER (s->toplevel_widget), + outer_vbox); + STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING); + gtk_container_add (GTK_CONTAINER (capplet), outer_vbox); + + /* Find the window above us, and set the title and close handler. */ + { + GtkWidget *window = capplet; + while (window && !GTK_IS_WINDOW (window)) + window = window->parent; + if (window) + { + gtk_window_set_title (GTK_WINDOW (window), window_title); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (wm_toplevel_close_cb), + (gpointer) s); + } + } + + s->toplevel_widget = capplet; + } +# endif /* HAVE_CRAPPLET */ + + + /* The Gnome folks hate the menubar. I think it's important to have access + to the commands on the File menu (Restart Daemon, etc.) and to the + About and Documentation commands on the Help menu. + */ +#if 0 +#ifdef HAVE_GTK2 + gtk_widget_hide (name_to_widget (s, "menubar")); +#endif +#endif + + free (window_title); + window_title = 0; + +#ifdef HAVE_GTK2 + /* After picking the default size, allow -geometry to override it. */ + if (geom) + gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom); +#endif + + gtk_widget_show (s->toplevel_widget); + init_icon (GTK_WIDGET (s->toplevel_widget)->window); /* after `show' */ + fix_preview_visual (s); + + /* Realize page zero, so that we can diddle the scrollbar when the + user tabs back to it -- otherwise, the current hack isn't scrolled + to the first time they tab back there, when started with "-prefs". + (Though it is if they then tab away, and back again.) + + #### Bah! This doesn't work. Gtk eats my ass! Someone who + #### understands this crap, explain to me how to make this work. + */ + gtk_widget_realize (name_to_widget (s, "demos_table")); + + + gtk_timeout_add (60 * 1000, check_blanked_timer, s); + + /* Issue any warnings about the running xscreensaver daemon. */ - the_network_is_not_the_computer (gtk_window); + the_network_is_not_the_computer (s); + /* Run the Gtk event loop, and not the Xt event loop. This means that if there were Xt timers or fds registered, they would never get serviced, @@ -1737,7 +4616,36 @@ main (int argc, char **argv) Xt so that we could process the command line and use the X resource manager. */ - gtk_main (); + s->initializing_p = False; + + /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds + after we start up. Otherwise, it always appears scrolled to the top + when in crapplet-mode. */ + gtk_timeout_add (500, delayed_scroll_kludge, s); + + +#if 0 + /* Load every configurator in turn, to scan them for errors all at once. */ + { + int i; + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + conf_data *d = load_configurator (hack->command, False); + if (d) free_conf_data (d); + } + } +#endif + + +# ifdef HAVE_CRAPPLET + if (crapplet_p) + capplet_gtk_main (); + else +# endif /* HAVE_CRAPPLET */ + gtk_main (); + + kill_preview_subproc (s, False); exit (0); }