+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, "<span foreground=\"#%02X%02X%02X\""
+ /* " background=\"#%02X%02X%02X\"" */
+ ">%s</span>",
+ 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);
+# 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
+\f
+/* 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 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 */
+
+\f
+/* 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);
+
+ /* 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;
+}
+
+
+\f
+/* 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
+clear_preview_window (state *s)
+{
+ GtkWidget *p;
+ GdkWindow *window;
+
+ if (!s->toplevel_widget) return; /* very early */
+ p = name_to_widget (s, "preview");
+ window = p->window;
+
+ 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
+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);
+}
+
+\f
+/* 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)
+{
+ 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);
+}
+
+
+/* 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 = pr->window;
+
+ s->running_preview_error_p = False;
+
+ if (s->preview_suppressed_p)
+ {
+ kill_preview_subproc (s);
+ 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);
+ 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;
+}
+