X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fdemo-Gtk.c;h=d1eb3c85089901c9bc840c201580c7dbc711fb85;hp=3a3f2d6f1c2a0aec019ca8276744511d46a8b4d7;hb=6b1c86cf395f59389e4ece4ea8f4bea2c332745b;hpb=723c9eeee862766a1534b2ce17b78adbfac1c3be diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c index 3a3f2d6f..d1eb3c85 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-2002 Jamie Zawinski + * xscreensaver, Copyright (c) 1993-2008 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 @@ -24,6 +24,13 @@ # 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 */ @@ -71,6 +78,10 @@ # include "xmu.h" #endif +#ifdef HAVE_XINERAMA +# include +#endif /* HAVE_XINERAMA */ + #include #ifdef HAVE_CRAPPLET @@ -81,8 +92,11 @@ #include #ifdef HAVE_GTK2 -#include -#endif /* HAVE_GTK2 */ +# include +# include +#else /* !HAVE_GTK2 */ +# define G_MODULE_EXPORT /**/ +#endif /* !HAVE_GTK2 */ #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR) # define GLADE_DIR DEFAULT_ICONDIR @@ -91,6 +105,13 @@ # 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 "version.h" #include "prefs.h" @@ -102,6 +123,9 @@ #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" @@ -120,18 +144,28 @@ enum { /* from exec.c */ extern void exec_command (const char *shell, const char *command, int nice); +extern int on_path_p (const char *program); + +static void hack_subproc_environment (Window preview_window_id, Bool debug_p); #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) +/* You might think that to read an array of 32-bit quantities out of a + server-side property, you would pass an array of 32-bit data quantities + into XGetWindowProperty(). You would be wrong. You have to use an array + of longs, even if long is 64 bits (using 32 of each 64.) + */ +typedef long PROP32; + 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 }; + DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME }; typedef struct { @@ -163,10 +197,17 @@ typedef struct { 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 total_available; /* how many 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 */ + int nscreens; /* How many X or Xinerama screens there are */ + saver_preferences prefs; } state; @@ -191,16 +232,52 @@ static Bool flush_popup_changes_and_save (state *); static int maybe_reload_init_file (state *); static void await_xscreensaver (state *); +static Bool xscreensaver_running_p (state *); +static void sensitize_menu_items (state *s, Bool force_p); +static void force_dialog_repaint (state *s); static void schedule_preview (state *, const char *cmd); -static void kill_preview_subproc (state *); +static void kill_preview_subproc (state *, Bool reset_p); static void schedule_preview_check (state *); + +/* Prototypes of functions used by the Glade-generated code, + to avoid warnings. + */ +void exit_menu_cb (GtkMenuItem *, gpointer user_data); +void about_menu_cb (GtkMenuItem *, gpointer user_data); +void doc_menu_cb (GtkMenuItem *, gpointer user_data); +void file_menu_cb (GtkMenuItem *, gpointer user_data); +void activate_menu_cb (GtkMenuItem *, gpointer user_data); +void lock_menu_cb (GtkMenuItem *, gpointer user_data); +void kill_menu_cb (GtkMenuItem *, gpointer user_data); +void restart_menu_cb (GtkWidget *, gpointer user_data); +void run_this_cb (GtkButton *, gpointer user_data); +void manual_cb (GtkButton *, gpointer user_data); +void run_next_cb (GtkButton *, gpointer user_data); +void run_prev_cb (GtkButton *, gpointer user_data); +void pref_changed_cb (GtkWidget *, gpointer user_data); +gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data); +void mode_menu_item_cb (GtkWidget *, gpointer user_data); +void switch_page_cb (GtkNotebook *, GtkNotebookPage *, + gint page_num, gpointer user_data); +void browse_image_dir_cb (GtkButton *, gpointer user_data); +void browse_text_file_cb (GtkButton *, gpointer user_data); +void browse_text_program_cb (GtkButton *, gpointer user_data); +void settings_cb (GtkButton *, gpointer user_data); +void settings_adv_cb (GtkButton *, gpointer user_data); +void settings_std_cb (GtkButton *, gpointer user_data); +void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *, + gint page_num, gpointer user_data); +void settings_cancel_cb (GtkButton *, gpointer user_data); +void settings_ok_cb (GtkButton *, gpointer user_data); /* Some random utility functions */ +const char *blurb (void); + const char * blurb (void) { @@ -229,16 +306,32 @@ name_to_widget (state *s, const char *name) #ifdef HAVE_GTK2 if (!s->glade_ui) { - s->glade_ui = glade_xml_new (GLADE_DIR "/xscreensaver-demo.glade2", - NULL, NULL); + /* 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" - " \"%s/xscreensaver-demo.glade2\"\n", - blurb(), GLADE_DIR); + "%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); } @@ -254,7 +347,8 @@ name_to_widget (state *s, const char *name) #endif /* HAVE_GTK2 */ if (w) return w; - fprintf (stderr, "%s: no widget \"%s\"\n", blurb(), name); + fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n", + blurb(), name); abort(); } @@ -273,12 +367,14 @@ ensure_selected_item_visible (GtkWidget *widget) selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); if (!gtk_tree_selection_get_selected (selection, &model, &iter)) - return; - - path = gtk_tree_model_get_path (model, &iter); + path = gtk_tree_path_new_first (); + else + path = gtk_tree_model_get_path (model, &iter); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget), - path, NULL, FALSE, 0.0, 0.0); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE); + + gtk_tree_path_free (path); + #else /* !HAVE_GTK2 */ GtkScrolledWindow *scroller = 0; @@ -402,7 +498,8 @@ warning_dialog (GtkWidget *parent, const char *message, while (parent && !parent->window) parent = parent->parent; - if (!GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */ + if (!parent || + !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */ { fprintf (stderr, "%s: too early for dialog?\n", progname); return; @@ -485,13 +582,13 @@ warning_dialog (GtkWidget *parent, const char *message, 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_SET_FLAGS (ok, GTK_CAN_DEFAULT); + STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT); gtk_widget_show (ok); gtk_widget_grab_focus (ok); if (cancel) { - GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); + STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); gtk_widget_show (cancel); } gtk_widget_show (label); @@ -535,6 +632,11 @@ run_cmd (state *s, Atom command, int arg) 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]; @@ -545,6 +647,9 @@ run_cmd (state *s, Atom command, int arg) warning_dialog (s->toplevel_widget, buf, False, 100); } if (err) free (err); + + sensitize_menu_items (s, True); + force_dialog_repaint (s); } @@ -552,46 +657,87 @@ static void run_hack (state *s, int list_elt, Bool report_errors_p) { int hack_number; + char *err = 0; + int status; + 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 + + status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1, + False, &err); + + if (status < 0 && report_errors_p) { - char *s = 0; - xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1, - False, &s); - if (s) free (s); + if (xscreensaver_running_p (s)) + { + /* Kludge: ignore the spurious "window unexpectedly deleted" + errors... */ + if (err && strstr (err, "unexpectedly deleted")) + status = 0; + + if (status < 0) + { + char buf [255]; + if (err) + sprintf (buf, "Error:\n\n%s", err); + else + strcpy (buf, "Unknown error!"); + warning_dialog (s->toplevel_widget, buf, False, 100); + } + } + else + { + /* The error is that the daemon isn't running; + offer to restart it. + */ + const char *d = DisplayString (GDK_DISPLAY()); + char msg [1024]; + sprintf (msg, + _("Warning:\n\n" + "The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%s\". Launch it now?"), + d); + warning_dialog (s->toplevel_widget, msg, True, 1); + } } + + if (err) free (err); + + sensitize_menu_items (s, False); } /* Button callbacks + + According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make + libglade work on Cygwin; apparently all Glade callbacks need this magic + extra declaration. I do not pretend to understand. */ -void +G_MODULE_EXPORT void exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ flush_dialog_changes_and_save (s); - kill_preview_subproc (s); + kill_preview_subproc (s, False); gtk_main_quit (); } -static void +static gboolean wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { state *s = (state *) data; flush_dialog_changes_and_save (s); gtk_main_quit (); + return TRUE; } -void +G_MODULE_EXPORT void about_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { char msg [2048]; @@ -604,10 +750,15 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data) *s = 0; s += 2; + /* 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-2002 %s"), s); + sprintf(copy, ("Copyright \xC2\xA9 1991-2008 %s"), s); #else /* !HAVE_GTK2 */ - sprintf(copy, _("Copyright \251 1991-2002 %s"), s); + sprintf(copy, ("Copyright \251 1991-2008 %s"), s); #endif /* !HAVE_GTK2 */ sprintf (msg, "%s\n\n%s", copy, desc); @@ -713,7 +864,7 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data) } -void +G_MODULE_EXPORT void doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -729,17 +880,27 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data) } help_command = (char *) malloc (strlen (p->load_url_command) + - (strlen (p->help_url) * 2) + 20); + (strlen (p->help_url) * 4) + 20); strcpy (help_command, "( "); sprintf (help_command + strlen(help_command), - p->load_url_command, p->help_url, p->help_url); + p->load_url_command, + p->help_url, p->help_url, p->help_url, p->help_url); strcat (help_command, " ) &"); - system (help_command); + if (system (help_command) < 0) + fprintf (stderr, "%s: fork error\n", blurb()); free (help_command); } -void +G_MODULE_EXPORT void +file_menu_cb (GtkMenuItem *menuitem, gpointer user_data) +{ + state *s = global_state_kludge; /* I hate C so much... */ + sensitize_menu_items (s, False); +} + + +G_MODULE_EXPORT void activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -747,7 +908,7 @@ activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data) } -void +G_MODULE_EXPORT void lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -755,7 +916,7 @@ lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data) } -void +G_MODULE_EXPORT void kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -763,45 +924,46 @@ kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data) } -void +G_MODULE_EXPORT void restart_menu_cb (GtkWidget *widget, gpointer user_data) { 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 &"); + if (system ("xscreensaver -nosplash &") < 0) + fprintf (stderr, "%s: fork error\n", blurb()); await_xscreensaver (s); } +static Bool +xscreensaver_running_p (state *s) +{ + Display *dpy = GDK_DISPLAY(); + char *rversion = 0; + server_xscreensaver_version (dpy, &rversion, 0, 0); + if (!rversion) + return False; + free (rversion); + return True; +} + static void await_xscreensaver (state *s) { int countdown = 5; + Bool ok = False; - Display *dpy = GDK_DISPLAY(); - /* GtkWidget *dialog = 0;*/ - char *rversion = 0; + while (!ok && (--countdown > 0)) + if (xscreensaver_running_p (s)) + ok = True; + else + sleep (1); /* If it's not there yet, wait a second... */ - while (!rversion && (--countdown > 0)) - { - /* Check for the version of the running xscreensaver... */ - server_xscreensaver_version (dpy, &rversion, 0, 0); - - /* If it's not there yet, wait a second... */ - if (!rversion) - sleep (1); - } + sensitize_menu_items (s, True); -/* if (dialog) gtk_widget_destroy (dialog);*/ - - if (rversion) - { - /* Got it. */ - free (rversion); - } - else + if (! ok) { /* Timed out, no screensaver running. */ @@ -814,7 +976,13 @@ await_xscreensaver (state *s) "\n")); if (root_p) - strcat (buf, + +# 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, STFU _("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" @@ -835,6 +1003,8 @@ await_xscreensaver (state *s) warning_dialog (s->toplevel_widget, buf, False, 1); } + + force_dialog_repaint (s); } @@ -848,6 +1018,7 @@ selected_list_element (state *s) static int demo_write_init_file (state *s, saver_preferences *p) { + Display *dpy = GDK_DISPLAY(); #if 0 /* #### try to figure out why shit keeps getting reordered... */ @@ -855,7 +1026,7 @@ demo_write_init_file (state *s, saver_preferences *p) abort(); #endif - if (!write_init_file (p, s->short_version, False)) + if (!write_init_file (dpy, p, s->short_version, False)) { if (s->debug_p) fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name()); @@ -880,7 +1051,7 @@ demo_write_init_file (state *s, saver_preferences *p) } -void +G_MODULE_EXPORT void run_this_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -891,40 +1062,44 @@ run_this_cb (GtkButton *button, gpointer user_data) } -void +G_MODULE_EXPORT void manual_cb (GtkButton *button, gpointer user_data) { + Display *dpy = GDK_DISPLAY(); state *s = global_state_kludge; /* I hate C so much... */ saver_preferences *p = &s->prefs; - GtkList *list_widget = GTK_LIST (name_to_widget (s, "list")); + GtkWidget *list_widget = name_to_widget (s, "list"); int list_elt = selected_list_element (s); int hack_number; char *name, *name2, *cmd, *str; + char *oname = 0; 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 (GTK_WIDGET (list_widget)); + ensure_selected_item_visible (list_widget); name = strdup (p->screenhacks[hack_number]->command); name2 = name; + oname = name; while (isspace (*name2)) name2++; str = name2; while (*str && !isspace (*str)) str++; *str = 0; str = strrchr (name2, '/'); - if (str) name = str+1; + if (str) name2 = str+1; - cmd = get_string_resource ("manualCommand", "ManualCommand"); + cmd = get_string_resource (dpy, "manualCommand", "ManualCommand"); if (cmd) { - char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100); + char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); strcpy (cmd2, "( "); sprintf (cmd2 + strlen (cmd2), cmd, name2, name2, name2, name2); strcat (cmd2, " ) &"); - system (cmd2); + if (system (cmd2) < 0) + fprintf (stderr, "%s: fork error\n", blurb()); free (cmd2); } else @@ -934,7 +1109,7 @@ manual_cb (GtkButton *button, gpointer user_data) False, 100); } - free (name); + free (oname); } @@ -953,9 +1128,11 @@ force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p) #ifdef HAVE_GTK2 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list)); 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); + if (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 */ @@ -964,11 +1141,11 @@ force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p) } -void +G_MODULE_EXPORT void run_next_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ - saver_preferences *p = &s->prefs; + /* saver_preferences *p = &s->prefs; */ Bool ops = s->preview_suppressed_p; GtkWidget *list_widget = name_to_widget (s, "list"); @@ -979,7 +1156,7 @@ run_next_cb (GtkButton *button, gpointer user_data) else list_elt++; - if (list_elt >= p->screenhacks_count) + if (list_elt >= s->list_count) list_elt = 0; s->preview_suppressed_p = True; @@ -993,23 +1170,23 @@ run_next_cb (GtkButton *button, gpointer user_data) } -void +G_MODULE_EXPORT void run_prev_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ - saver_preferences *p = &s->prefs; + /* saver_preferences *p = &s->prefs; */ Bool ops = s->preview_suppressed_p; GtkWidget *list_widget = name_to_widget (s, "list"); int list_elt = selected_list_element (s); if (list_elt < 0) - list_elt = p->screenhacks_count - 1; + list_elt = s->list_count - 1; else list_elt--; if (list_elt < 0) - list_elt = p->screenhacks_count - 1; + list_elt = s->list_count - 1; s->preview_suppressed_p = True; @@ -1037,7 +1214,7 @@ flush_changes (state *s, Bool changed = False; screenhack *hack; int hack_number; - if (list_elt < 0 || list_elt >= p->screenhacks_count) + if (list_elt < 0 || list_elt >= s->list_count) abort(); hack_number = s->list_elt_to_hack_number[list_elt]; @@ -1100,7 +1277,7 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) else { char c; - if (sscanf (line, "%u%c", &value, &c) != 1) + if (sscanf (line, "%d%c", &value, &c) != 1) value = -1; if (!sec_p) value *= 60; @@ -1136,13 +1313,27 @@ directory_p (const char *path) return True; } +static Bool +file_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; +} + static char * normalize_directory (const char *path) { int L; char *p2, *s; - if (!path) return 0; - L = strlen (path);; + if (!path || !*path) return 0; + L = strlen (path); p2 = (char *) malloc (L + 2); strcpy (p2, path); if (p2[L-1] == '/') /* remove trailing slash */ @@ -1287,6 +1478,10 @@ flush_dialog_changes_and_save (state *s) w = name_to_widget (s, (NAME)); \ (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w))) +# define TEXT(FIELD,NAME) \ + w = name_to_widget (s, (NAME)); \ + (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w)) + MINUTES (&p2->timeout, "timeout_spinbutton"); MINUTES (&p2->cycle, "cycle_spinbutton"); CHECKBOX (p2->lock_p, "lock_button"); @@ -1302,9 +1497,25 @@ flush_dialog_changes_and_save (state *s) CHECKBOX (p2->random_image_p, "grab_image_button"); PATHNAME (p2->image_directory, "image_text"); +#if 0 CHECKBOX (p2->verbose_p, "verbose_button"); CHECKBOX (p2->capture_stderr_p, "capture_button"); CHECKBOX (p2->splash_p, "splash_button"); +#endif + + { + Bool v = False; + CHECKBOX (v, "text_host_radio"); if (v) p2->tmode = TEXT_DATE; + CHECKBOX (v, "text_radio"); if (v) p2->tmode = TEXT_LITERAL; + CHECKBOX (v, "text_file_radio"); if (v) p2->tmode = TEXT_FILE; + CHECKBOX (v, "text_program_radio"); if (v) p2->tmode = TEXT_PROGRAM; + CHECKBOX (v, "text_url_radio"); if (v) p2->tmode = TEXT_URL; + TEXT (p2->text_literal, "text_entry"); + PATHNAME (p2->text_file, "text_file_entry"); + PATHNAME (p2->text_program, "text_program_entry"); + PATHNAME (p2->text_program, "text_program_entry"); + TEXT (p2->text_url, "text_url_entry"); + } CHECKBOX (p2->install_cmap_p, "install_button"); CHECKBOX (p2->fade_p, "fade_button"); @@ -1315,6 +1526,7 @@ flush_dialog_changes_and_save (state *s) # undef MINUTES # undef CHECKBOX # undef PATHNAME +# undef TEXT /* Warn if the image directory doesn't exist. */ @@ -1352,7 +1564,7 @@ flush_dialog_changes_and_save (state *s) if (p->field != p2->field) { \ changed = True; \ if (s->debug_p) \ - fprintf (stderr, "%s: %s => %d\n", blurb(), name, p2->field); \ + fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \ } \ p->field = p2->field @@ -1369,9 +1581,13 @@ flush_dialog_changes_and_save (state *s) COPY(dpms_suspend, "dpms_suspend"); COPY(dpms_off, "dpms_off"); +#if 0 COPY(verbose_p, "verbose_p"); COPY(capture_stderr_p, "capture_stderr_p"); COPY(splash_p, "splash_p"); +#endif + + COPY(tmode, "tmode"); COPY(install_cmap_p, "install_cmap_p"); COPY(fade_p, "fade_p"); @@ -1384,19 +1600,26 @@ flush_dialog_changes_and_save (state *s) # 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; +# define COPYSTR(FIELD,NAME) \ + if (!p->FIELD || \ + !p2->FIELD || \ + strcmp(p->FIELD, p2->FIELD)) \ + { \ + changed = True; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \ + } \ + if (p->FIELD && p->FIELD != p2->FIELD) \ + free (p->FIELD); \ + p->FIELD = p2->FIELD; \ + p2->FIELD = 0 + + COPYSTR(image_directory, "image_directory"); + COPYSTR(text_literal, "text_literal"); + COPYSTR(text_file, "text_file"); + COPYSTR(text_program, "text_program"); + COPYSTR(text_url, "text_url"); +# undef COPYSTR populate_prefs_page (s); @@ -1470,7 +1693,7 @@ flush_popup_changes_and_save (state *s) 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, " %lu %c", &id, &c)) ; else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; else { @@ -1495,7 +1718,7 @@ flush_popup_changes_and_save (state *s) } -void +G_MODULE_EXPORT void pref_changed_cb (GtkWidget *widget, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1507,7 +1730,7 @@ pref_changed_cb (GtkWidget *widget, gpointer user_data) } } -gboolean +G_MODULE_EXPORT gboolean pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { pref_changed_cb (widget, user_data); @@ -1516,7 +1739,7 @@ pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) /* Callback on menu items in the "mode" options menu. */ -void +G_MODULE_EXPORT void mode_menu_item_cb (GtkWidget *widget, gpointer user_data) { state *s = (state *) user_data; @@ -1527,7 +1750,6 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent)); int menu_index = 0; saver_mode new_mode; - int old_selected = p->selected_hack; while (menu_items) { @@ -1541,17 +1763,12 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) new_mode = mode_menu_order[menu_index]; /* Keep the same list element displayed as before; except if we're - switching *to* "one screensaver" mode from any other mode, scroll - to and select "the one". + switching *to* "one screensaver" mode from any other mode, set + "the one" to be that which is currently selected. */ - list_elt = -1; + list_elt = selected_list_element (s); if (new_mode == ONE_HACK) - list_elt = (p->selected_hack >= 0 - ? s->hack_number_to_list_elt[p->selected_hack] - : -1); - - if (list_elt < 0) - list_elt = selected_list_element (s); + p->selected_hack = s->list_elt_to_hack_number[list_elt]; { saver_mode old_mode = p->mode; @@ -1562,13 +1779,10 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) } pref_changed_cb (widget, user_data); - - if (old_selected != p->selected_hack) - abort(); /* dammit, not again... */ } -void +G_MODULE_EXPORT void switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, gint page_num, gpointer user_data) { @@ -1594,7 +1808,7 @@ list_activated_cb (GtkTreeView *list, char *str; int list_elt; - g_return_if_fail (!gdk_pointer_is_grabbed ()); + STFU g_return_if_fail (!gdk_pointer_is_grabbed ()); str = gtk_tree_path_to_string (path); list_elt = strtol (str, NULL, 10); @@ -1626,6 +1840,11 @@ list_select_changed_cb (GtkTreeSelection *selection, gpointer data) populate_demo_window (s, list_elt); flush_dialog_changes_and_save (s); + + /* Re-populate the Settings window any time a new item is selected + in the list, in case both windows are currently visible. + */ + populate_popup_window (s); } #else /* !HAVE_GTK2 */ @@ -1790,6 +2009,69 @@ store_image_directory (GtkWidget *button, gpointer user_data) } +static void +store_text_file (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->text_file && !strcmp(p->text_file, path)) + return; /* no change */ + + if (!file_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, False, 100); + return; + } + + if (p->text_file) free (p->text_file); + p->text_file = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + (p->text_file ? p->text_file : "")); + demo_write_init_file (s, p); +} + + +static void +store_text_program (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->text_program && !strcmp(p->text_program, path)) + return; /* no change */ + +# if 0 + if (!file_p (path)) + { + char b[255]; + sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path); + warning_dialog (GTK_WIDGET (top), b, False, 100); + return; + } +# endif + + if (p->text_program) free (p->text_program); + p->text_program = normalize_directory (path); + + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + (p->text_program ? p->text_program : "")); + demo_write_init_file (s, p); +} + + + static void browse_image_dir_cancel (GtkWidget *button, gpointer user_data) { @@ -1804,6 +2086,20 @@ browse_image_dir_ok (GtkWidget *button, gpointer user_data) store_image_directory (button, user_data); } +static void +browse_text_file_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_text_file (button, user_data); +} + +static void +browse_text_program_ok (GtkWidget *button, gpointer user_data) +{ + browse_image_dir_cancel (button, user_data); + store_text_program (button, user_data); +} + static void browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) { @@ -1811,7 +2107,7 @@ browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) } -void +G_MODULE_EXPORT void browse_image_dir_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1847,7 +2143,78 @@ browse_image_dir_cb (GtkButton *button, gpointer user_data) } -void +G_MODULE_EXPORT void +browse_text_file_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 a text file.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->text_file && *p->text_file) + gtk_file_selection_set_filename (selector, p->text_file); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_text_file_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_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + +G_MODULE_EXPORT void +browse_text_program_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 a text-generating program.")); + + if (!fsd) + fsd = (file_selection_data *) malloc (sizeof (*fsd)); + + fsd->widget = selector; + fsd->state = s; + + if (p->text_program && *p->text_program) + gtk_file_selection_set_filename (selector, p->text_program); + + gtk_signal_connect (GTK_OBJECT (selector->ok_button), + "clicked", GTK_SIGNAL_FUNC (browse_text_program_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_window_set_modal (GTK_WINDOW (selector), True); + gtk_widget_show (GTK_WIDGET (selector)); +} + + + + + +G_MODULE_EXPORT void settings_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1870,7 +2237,7 @@ settings_sync_cmd_text (state *s) # endif /* HAVE_XML */ } -void +G_MODULE_EXPORT void settings_adv_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1881,7 +2248,7 @@ settings_adv_cb (GtkButton *button, gpointer user_data) gtk_notebook_set_page (notebook, 1); } -void +G_MODULE_EXPORT void settings_std_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1894,7 +2261,7 @@ settings_std_cb (GtkButton *button, gpointer user_data) gtk_notebook_set_page (notebook, 0); } -void +G_MODULE_EXPORT void settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, gint page_num, gpointer user_data) { @@ -1918,14 +2285,14 @@ settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, -void +G_MODULE_EXPORT 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 +G_MODULE_EXPORT void settings_ok_cb (GtkButton *button, gpointer user_data) { state *s = global_state_kludge; /* I hate C so much... */ @@ -1942,11 +2309,12 @@ settings_ok_cb (GtkButton *button, gpointer user_data) gtk_widget_hide (s->popup_widget); } -static void +static gboolean wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { state *s = (state *) data; settings_cancel_cb (0, (gpointer) s); + return TRUE; } @@ -1963,7 +2331,7 @@ server_current_hack (void) Atom type; int format; unsigned long nitems, bytesafter; - CARD32 *data = 0; + unsigned char *dataP = 0; Display *dpy = GDK_DISPLAY(); int hack_number = -1; @@ -1971,32 +2339,55 @@ server_current_hack (void) XA_SCREENSAVER_STATUS, 0, 3, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, - (unsigned char **) &data) + &dataP) == Success && type == XA_INTEGER && nitems >= 3 - && data) - hack_number = (int) data[2] - 1; + && dataP) + { + PROP32 *data = (PROP32 *) dataP; + hack_number = (int) data[2] - 1; + } - if (data) free (data); + if (dataP) XFree (dataP); return hack_number; } -/* Finds the number of the last hack to run, and makes that item be +/* Finds the number of the last hack that was 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; + int hack_number = -1; - if (p->mode == ONE_HACK) + if (p->mode == ONE_HACK) /* in "one" mode, use the one */ hack_number = p->selected_hack; - else + if (hack_number < 0) /* otherwise, use the last-run */ hack_number = server_current_hack (); + if (hack_number < 0) /* failing that, last "one mode" */ + hack_number = p->selected_hack; + if (hack_number < 0) /* failing that, newest hack. */ + { + /* We should only get here if the user does not have a .xscreensaver + file, and the screen has not been blanked with a hack since X + started up: in other words, this is probably a fresh install. + + Instead of just defaulting to hack #0 (in either "programs" or + "alphabetical" order) let's try to default to the last runnable + hack in the "programs" list: this is probably the hack that was + most recently added to the xscreensaver distribution (and so + it's probably the currently-coolest one!) + */ + hack_number = p->screenhacks_count-1; + while (hack_number > 0 && + ! (s->hacks_available_p[hack_number] && + p->screenhacks[hack_number]->enabled_p)) + hack_number--; + } if (hack_number >= 0 && hack_number < p->screenhacks_count) { @@ -2011,6 +2402,7 @@ scroll_to_current_hack (state *s) static void populate_hack_list (state *s) { + Display *dpy = GDK_DISPLAY(); #ifdef HAVE_GTK2 saver_preferences *p = &s->prefs; GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); @@ -2042,7 +2434,7 @@ populate_hack_list (state *s) ren = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (list, COL_NAME, _("Screen Saver"), ren, - "text", COL_NAME, + "markup", COL_NAME, NULL); g_signal_connect_after (list, "row_activated", @@ -2056,20 +2448,49 @@ populate_hack_list (state *s) } - for (i = 0; i < p->screenhacks_count; i++) + for (i = 0; i < s->list_count; i++) { - screenhack *hack = p->screenhacks[s->list_elt_to_hack_number[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]); - char *pretty_name = (hack->name - ? strdup (hack->name) - : make_hack_name (hack->command)); + 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 (dpy, 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); } @@ -2078,9 +2499,10 @@ populate_hack_list (state *s) saver_preferences *p = &s->prefs; GtkList *list = GTK_LIST (name_to_widget (s, "list")); int i; - for (i = 0; i < p->screenhacks_count; i++) + for (i = 0; i < s->list_count; i++) { - screenhack *hack = p->screenhacks[s->list_elt_to_hack_number[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 @@ -2092,10 +2514,18 @@ populate_hack_list (state *s) GtkWidget *line_hbox; GtkWidget *line_check; GtkWidget *line_label; + char *pretty_name; + Bool available_p = (hack && s->hacks_available_p [hack_number]); - char *pretty_name = (hack->name - ? strdup (hack->name) - : make_hack_name (hack->command)); + 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); @@ -2126,11 +2556,30 @@ populate_hack_list (state *s) GTK_SIGNAL_FUNC (list_checkbox_cb), (gpointer) s); -#if 0 /* #### */ - GTK_WIDGET (GTK_BIN(line)->child)->style = - gtk_style_copy (GTK_WIDGET (text_line)->style); -#endif 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", @@ -2146,8 +2595,11 @@ 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 sensitive = (p->mode == RANDOM_HACKS || + p->mode == RANDOM_HACKS_SAME || + p->mode == ONE_HACK); + Bool checkable = (p->mode == RANDOM_HACKS || + p->mode == RANDOM_HACKS_SAME); Bool blankable = (p->mode != DONT_BLANK); #ifndef HAVE_GTK2 @@ -2203,6 +2655,22 @@ 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 + + + /* If there is only one screen, the mode menu contains + "random" but not "random-same". + */ + if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME) + p->mode = RANDOM_HACKS; + + /* 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...) @@ -2210,7 +2678,7 @@ populate_prefs_page (state *s) # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000 THROTTLE (timeout); THROTTLE (cycle); - THROTTLE (passwd_timeout); + /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */ # undef THROTTLE # define FMT_MINUTES(NAME,N) \ @@ -2235,9 +2703,11 @@ populate_prefs_page (state *s) (ACTIVEP)) TOGGLE_ACTIVE ("lock_button", p->lock_p); +#if 0 TOGGLE_ACTIVE ("verbose_button", p->verbose_p); TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p); TOGGLE_ACTIVE ("splash_button", p->splash_p); +#endif 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); @@ -2246,6 +2716,15 @@ populate_prefs_page (state *s) TOGGLE_ACTIVE ("fade_button", p->fade_p); TOGGLE_ACTIVE ("unfade_button", p->unfade_p); + switch (p->tmode) + { + case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio", True); break; + case TEXT_FILE: TOGGLE_ACTIVE ("text_file_radio", True); break; + case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break; + case TEXT_URL: TOGGLE_ACTIVE ("text_url_radio", True); break; + default: TOGGLE_ACTIVE ("text_host_radio", True); break; + } + # undef TOGGLE_ACTIVE gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), @@ -2255,6 +2734,29 @@ populate_prefs_page (state *s) gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"), p->random_image_p); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")), + (p->text_literal ? p->text_literal : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + (p->text_file ? p->text_file : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + (p->text_program ? p->text_program : "")); + gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")), + (p->text_url ? p->text_url : "")); + + gtk_widget_set_sensitive (name_to_widget (s, "text_entry"), + p->tmode == TEXT_LITERAL); + gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"), + p->tmode == TEXT_FILE); + gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"), + p->tmode == TEXT_FILE); + gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"), + p->tmode == TEXT_PROGRAM); + gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"), + p->tmode == TEXT_PROGRAM); + gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"), + p->tmode == TEXT_URL); + + /* Map the `saver_mode' enum to mode menu to values. */ { GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu")); @@ -2269,10 +2771,11 @@ populate_prefs_page (state *s) { Bool found_any_writable_cells = False; + Bool fading_possible = False; Bool dpms_supported = False; Display *dpy = GDK_DISPLAY(); - int nscreens = ScreenCount(dpy); + int nscreens = ScreenCount(dpy); /* real screens, not Xinerama */ int i; for (i = 0; i < nscreens; i++) { @@ -2284,8 +2787,9 @@ populate_prefs_page (state *s) } } + fading_possible = found_any_writable_cells; #ifdef HAVE_XF86VMODE_GAMMA - found_any_writable_cells = True; /* if we can gamma fade, go for it */ + fading_possible = True; #endif #ifdef HAVE_DPMS_EXTENSION @@ -2302,8 +2806,9 @@ populate_prefs_page (state *s) /* Blanking and Locking */ - SENSITIZE ("lock_spinbutton", p->lock_p); - SENSITIZE ("lock_mlabel", p->lock_p); + 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 */ @@ -2321,14 +2826,14 @@ populate_prefs_page (state *s) /* Colormaps */ - SENSITIZE ("cmap_frame", found_any_writable_cells); + SENSITIZE ("cmap_frame", found_any_writable_cells || fading_possible); SENSITIZE ("install_button", found_any_writable_cells); - SENSITIZE ("fade_button", found_any_writable_cells); - SENSITIZE ("unfade_button", found_any_writable_cells); + SENSITIZE ("fade_button", fading_possible); + SENSITIZE ("unfade_button", fading_possible); - SENSITIZE ("fade_label", (found_any_writable_cells && + SENSITIZE ("fade_label", (fading_possible && (p->fade_p || p->unfade_p))); - SENSITIZE ("fade_spinbutton", (found_any_writable_cells && + SENSITIZE ("fade_spinbutton", (fading_possible && (p->fade_p || p->unfade_p))); # undef SENSITIZE @@ -2339,14 +2844,7 @@ populate_prefs_page (state *s) static void populate_popup_window (state *s) { - saver_preferences *p = &s->prefs; - GtkWidget *parent = name_to_widget (s, "settings_vbox"); GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc")); - int list_elt = selected_list_element (s); - int hack_number = (list_elt >= 0 && list_elt < p->screenhacks_count - ? s->list_elt_to_hack_number[list_elt] - : -1); - screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0); char *doc_string = 0; /* #### not in Gtk 1.2 @@ -2360,14 +2858,24 @@ populate_popup_window (state *s) s->cdata = 0; } - if (hack) - { - 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); - } + { + 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 @@ -2385,23 +2893,78 @@ populate_popup_window (state *s) 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" }; + const char *names[] = { "demo", "settings", + "cmd_label", "cmd_text", "manual", + "visual", "visual_combo" }; int i; - for (i = 0; i < countof(names1); i++) + for (i = 0; i < countof(names); i++) { - GtkWidget *w = name_to_widget (s, names1[i]); + GtkWidget *w = name_to_widget (s, names[i]); gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); } - for (i = 0; i < countof(names2); i++) +} + + +static void +sensitize_menu_items (state *s, Bool force_p) +{ + static Bool running_p = False; + static time_t last_checked = 0; + time_t now = time ((time_t *) 0); + const char *names[] = { "activate_menu", "lock_menu", "kill_menu", + /* "demo" */ }; + int i; + + if (force_p || now > last_checked + 10) /* check every 10 seconds */ + { + running_p = xscreensaver_running_p (s); + last_checked = time ((time_t *) 0); + } + + for (i = 0; i < countof(names); i++) { - GtkWidget *w = name_to_widget (s, names2[i]); - gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + GtkWidget *w = name_to_widget (s, names[i]); + gtk_widget_set_sensitive (GTK_WIDGET(w), running_p); } } +/* When the File menu is de-posted after a "Restart Daemon" command, + the window underneath doesn't repaint for some reason. I guess this + is a bug in exposure handling in GTK or GDK. This works around it. + */ +static void +force_dialog_repaint (state *s) +{ +#if 1 + /* Tell GDK to invalidate and repaint the whole window. + */ + GdkWindow *w = s->toplevel_widget->window; + GdkRegion *region = gdk_region_new (); + GdkRectangle rect; + rect.x = rect.y = 0; + rect.width = rect.height = 32767; + gdk_region_union_with_rect (region, &rect); + gdk_window_invalidate_region (w, region, True); + gdk_region_destroy (region); + gdk_window_process_updates (w, True); +#else + /* Force the server to send an exposure event by creating and then + destroying a window as a child of the top level shell. + */ + Display *dpy = GDK_DISPLAY(); + Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window); + Window w; + XWindowAttributes xgwa; + XGetWindowAttributes (dpy, parent, &xgwa); + w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0); + XMapRaised (dpy, w); + XDestroyWindow (dpy, w); + XSync (dpy, False); +#endif +} + + /* 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. @@ -2409,7 +2972,9 @@ sensitize_demo_widgets (state *s, Bool sensitive_p) static void fix_text_entry_sizes (state *s) { -#ifdef FIXME + 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", @@ -2417,7 +2982,6 @@ fix_text_entry_sizes (state *s) "-fade_spinbutton" }; int i; int width = 0; - GtkWidget *w; for (i = 0; i < countof(spinbuttons); i++) { @@ -2448,22 +3012,35 @@ fix_text_entry_sizes (state *s) width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm"); gtk_widget_set_usize (w, width, -2); - /* Now fix the height of the list. +# 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; - w = GTK_WIDGET (name_to_widget (s, "list")); + +#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); } -#endif } @@ -2569,6 +3146,7 @@ map_prev_button_cb (GtkWidget *w, gpointer user_data) #endif /* !HAVE_GTK2 */ +#ifndef HAVE_GTK2 /* Work around a Gtk bug that causes label widgets to wrap text too early. */ @@ -2590,7 +3168,6 @@ you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label, gtk_widget_size_request (label, &req); } - /* Feel the love. Thanks to Nat Friedman for finding this workaround. */ static void @@ -2612,11 +3189,13 @@ eschew_gtk_lossage (GtkLabel *label) gtk_widget_queue_resize (GTK_WIDGET (label)); } +#endif /* !HAVE_GTK2 */ static void populate_demo_window (state *s, int list_elt) { + Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; screenhack *hack; char *pretty_name; @@ -2640,7 +3219,7 @@ populate_demo_window (state *s, int list_elt) } else { - int hack_number = (list_elt >= 0 && list_elt < p->screenhacks_count + 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); @@ -2648,7 +3227,7 @@ populate_demo_window (state *s, int list_elt) pretty_name = (hack ? (hack->name ? strdup (hack->name) - : make_hack_name (hack->command)) + : make_hack_name (dpy, hack->command)) : 0); if (hack) @@ -2660,15 +3239,15 @@ populate_demo_window (state *s, int list_elt) if (!pretty_name) pretty_name = strdup (_("Preview")); - gtk_frame_set_label (frame1, pretty_name); - gtk_frame_set_label (frame2, pretty_name); + 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", + sprintf (title, _("%s: %.100s Settings"), progclass, (pretty_name ? pretty_name : "???")); gtk_window_set_title (GTK_WINDOW (s->popup_widget), title); } @@ -2716,30 +3295,62 @@ sort_hack_cmp (const void *a, const void *b) if (a == b) return 0; else - return strcmp (sort_hack_cmp_names_kludge[*(int *) a], - sort_hack_cmp_names_kludge[*(int *) b]); + { + 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) { + Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; - int i; + 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); + s->total_available = 0; - /* Initialize table to 1:1 mapping */ + /* Check which hacks actually exist on $PATH + */ for (i = 0; i < p->screenhacks_count; i++) - s->list_elt_to_hack_number[i] = i; + { + screenhack *hack = p->screenhacks[i]; + int on = on_path_p (hack->command) ? 1 : 0; + s->hacks_available_p[i] = on; + s->total_available += on; + } - /* Generate list of names (once) + /* 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); @@ -2748,14 +3359,14 @@ initialize_sort_map (state *s) screenhack *hack = p->screenhacks[i]; char *name = (hack->name && *hack->name ? strdup (hack->name) - : make_hack_name (hack->command)); + : make_hack_name (dpy, hack->command)); char *str; for (str = name; *str; str++) *str = tolower(*str); sort_hack_cmp_names_kludge[i] = name; } - /* Sort alphabetically + /* Sort list->hack map alphabetically */ qsort (s->list_elt_to_hack_number, p->screenhacks_count, @@ -2771,13 +3382,18 @@ initialize_sort_map (state *s) /* 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; + { + int n = s->list_elt_to_hack_number[i]; + if (n != -1) + s->hack_number_to_list_elt[n] = i; + } } static int maybe_reload_init_file (state *s) { + Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; int status = 0; @@ -2801,7 +3417,7 @@ maybe_reload_init_file (state *s) warning_dialog (s->toplevel_widget, b, False, 100); free (b); - load_init_file (p); + load_init_file (dpy, p); initialize_sort_map (s); list_elt = selected_list_element (s); @@ -2860,41 +3476,81 @@ clear_preview_window (state *s) gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]); gdk_window_clear (window); -#ifdef HAVE_GTK2 { - GtkWidget *notebook; + 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); + Bool nothing_p = (s->total_available < 5); - notebook = name_to_widget (s, "preview_notebook"); +#ifdef HAVE_GTK2 + GtkWidget *notebook = name_to_widget (s, "preview_notebook"); gtk_notebook_set_page (GTK_NOTEBOOK (notebook), - s->running_preview_error_p - ? 1 : 0); - } + (s->running_preview_error_p + ? (available_p ? 1 : + nothing_p ? 3 : 2) + : 0)); #else /* !HAVE_GTK2 */ - if (s->running_preview_error_p) - { - const char * const lines[] = { N_("No Preview"), N_("Available") }; - int lh = p->style->font->ascent + p->style->font->descent; - int y, i; - gint w, h; - gdk_window_get_size (window, &w, &h); - y = (h - (lh * countof(lines))) / 2; - y += p->style->font->ascent; - for (i = 0; i < countof(lines); 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; - } - } + 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) { @@ -2938,6 +3594,11 @@ fix_preview_visual (state *s) 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); @@ -2956,8 +3617,13 @@ subproc_pretty_name (state *s) char *ss = strchr (ps, ' '); if (ss) *ss = 0; ss = strrchr (ps, '/'); - if (ss) *ss = 0; - else ss = ps; + if (!ss) + ss = ps; + else + { + ss = strdup (ss+1); + free (ps); + } return ss; } else @@ -2977,11 +3643,13 @@ reap_zombies (state *s) if (pid == s->running_preview_pid) { char *ss = subproc_pretty_name (s); - fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), pid, ss); + fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), + (unsigned long) pid, ss); free (ss); } else - fprintf (stderr, "%s: pid %lu died\n", blurb(), pid); + fprintf (stderr, "%s: pid %lu died\n", blurb(), + (unsigned long) pid); } } } @@ -3042,7 +3710,14 @@ get_best_gl_visual (state *s) sprintf (buf, "%s: running %s", blurb(), av[0]); perror (buf); } - exit (1); /* exits fork */ + + /* 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: @@ -3051,13 +3726,14 @@ get_best_gl_visual (state *s) int wait_status = 0; FILE *f = fdopen (in, "r"); - unsigned long v = 0; + unsigned int v = 0; char c; close (out); /* don't need this one */ *buf = 0; - fgets (buf, sizeof(buf)-1, f); + if (!fgets (buf, sizeof(buf)-1, f)) + *buf = 0; fclose (f); /* Wait for the child to die. */ @@ -3090,7 +3766,7 @@ get_best_gl_visual (state *s) static void -kill_preview_subproc (state *s) +kill_preview_subproc (state *s, Bool reset_p) { s->running_preview_error_p = False; @@ -3115,19 +3791,23 @@ kill_preview_subproc (state *s) { if (s->debug_p) fprintf (stderr, "%s: pid %lu (%s) was already dead.\n", - blurb(), s->running_preview_pid, ss); + blurb(), (unsigned long) s->running_preview_pid, ss); } else { char buf [1024]; sprintf (buf, "%s: couldn't kill pid %lu (%s)", - blurb(), s->running_preview_pid, ss); + blurb(), (unsigned long) s->running_preview_pid, ss); perror (buf); } } - else if (s->debug_p) - fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(), - s->running_preview_pid, ss); + else { + int endstatus; + waitpid(s->running_preview_pid, &endstatus, 0); + 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; @@ -3136,6 +3816,12 @@ kill_preview_subproc (state *s) } reap_zombies (s); + + if (reset_p) + { + reset_preview_window (s); + clear_preview_window (s); + } } @@ -3152,13 +3838,17 @@ launch_preview_subproc (state *s) const char *cmd = s->desired_preview_cmd; GtkWidget *pr = name_to_widget (s, "preview"); - GdkWindow *window = pr->window; + GdkWindow *window; + + reset_preview_window (s); + + window = pr->window; s->running_preview_error_p = False; if (s->preview_suppressed_p) { - kill_preview_subproc (s); + kill_preview_subproc (s, False); goto DONE; } @@ -3174,10 +3864,11 @@ launch_preview_subproc (state *s) else { strcpy (new_cmd, cmd); - sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", id); + sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", + (unsigned int) id); } - kill_preview_subproc (s); + kill_preview_subproc (s, False); if (! new_cmd) { s->running_preview_error_p = True; @@ -3200,6 +3891,8 @@ launch_preview_subproc (state *s) { 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 @@ -3207,8 +3900,15 @@ launch_preview_subproc (state *s) 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. */ - exit (1); /* exits child fork */ + 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: @@ -3220,7 +3920,8 @@ launch_preview_subproc (state *s) if (s->debug_p) { char *ss = subproc_pretty_name (s); - fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), forked, ss); + fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), + (unsigned long) forked, ss); free (ss); } break; @@ -3282,6 +3983,29 @@ hack_environment (state *s) } +static void +hack_subproc_environment (Window preview_window_id, Bool debug_p) +{ + /* 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); + + /* 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 (); + + if (debug_p) + fprintf (stderr, "%s: %s\n", blurb(), nssw); + + /* do not free(nssw) -- see above */ +} + + /* Called from a timer: Launches the currently-chosen subprocess, if it's not already running. If there's a different process running, kills it. @@ -3291,7 +4015,7 @@ update_subproc_timer (gpointer data) { state *s = (state *) data; if (! s->desired_preview_cmd) - kill_preview_subproc (s); + kill_preview_subproc (s, True); else if (!s->running_preview_cmd || !!strcmp (s->desired_preview_cmd, s->running_preview_cmd)) launch_preview_subproc (s); @@ -3354,7 +4078,7 @@ check_subproc_timer (gpointer data) { char *ss = subproc_pretty_name (s); fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(), - s->running_preview_pid, ss, + (unsigned long) s->running_preview_pid, ss, (s->running_preview_error_p ? "dead" : "alive")); free (ss); } @@ -3414,7 +4138,7 @@ screen_blanked_p (void) Atom type; int format; unsigned long nitems, bytesafter; - CARD32 *data = 0; + unsigned char *dataP = 0; Display *dpy = GDK_DISPLAY(); Bool blanked_p = False; @@ -3422,14 +4146,17 @@ screen_blanked_p (void) XA_SCREENSAVER_STATUS, 0, 3, False, XA_INTEGER, &type, &format, &nitems, &bytesafter, - (unsigned char **) &data) + &dataP) == Success && type == XA_INTEGER && nitems >= 3 - && data) - blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK); + && dataP) + { + Atom *data = (Atom *) dataP; + blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK); + } - if (data) free (data); + if (dataP) XFree (dataP); return blanked_p; } @@ -3447,12 +4174,35 @@ check_blanked_timer (gpointer data) { if (s->debug_p) fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb()); - kill_preview_subproc (s); + kill_preview_subproc (s, True); } return True; /* re-execute timer */ } + +/* How many screens are there (including Xinerama.) + */ +static int +screen_count (Display *dpy) +{ + int nscreens = ScreenCount(dpy); +# ifdef HAVE_XINERAMA + if (nscreens <= 1) + { + int event_number, error_number; + if (XineramaQueryExtension (dpy, &event_number, &error_number) && + XineramaIsActive (dpy)) + { + XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens); + if (xsi) XFree (xsi); + } + } +# endif /* HAVE_XINERAMA */ + + return nscreens; +} + /* Setting window manager icon */ @@ -3563,11 +4313,11 @@ the_network_is_not_the_computer (state *s) "xscreensaver as \"%s\".\n" "\n" "Restart the xscreensaver daemon now?\n"), - blurb(), luser, lhost, + progname, luser, lhost, d, (ruser ? ruser : "???"), (rhost ? rhost : "???"), - blurb(), - blurb(), (ruser ? ruser : "???"), + progname, + progname, (ruser ? ruser : "???"), luser); } else if (rhost && *rhost && !!strcmp (rhost, lhost)) @@ -3585,11 +4335,11 @@ the_network_is_not_the_computer (state *s) "%s won't work right.\n" "\n" "Restart the daemon on \"%s\" as \"%s\" now?\n"), - blurb(), luser, lhost, + progname, luser, lhost, d, (ruser ? ruser : "???"), (rhost ? rhost : "???"), luser, - blurb(), + progname, lhost, luser); } else if (!!strcmp (rversion, s->short_version)) @@ -3628,7 +4378,7 @@ demo_ehandler (Display *dpy, XErrorEvent *error) 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); + kill_preview_subproc (s, False); exit (-1); return 0; } @@ -3665,6 +4415,13 @@ 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 + +STFU static char *defaults[] = { #include "XScreenSaver_ad.h" 0 @@ -3682,16 +4439,20 @@ const char *usage = "[--display dpy] [--prefs]" # ifdef HAVE_CRAPPLET " [--crapplet]" # endif - "\n\t\t [--debug] [--sync] [--no-xshm]"; + "\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; +#ifndef HAVE_GTK2 GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc")); +#endif s->initializing_p = True; +#ifndef HAVE_GTK2 eschew_gtk_lossage (label); +#endif s->initializing_p = oi; } @@ -3780,7 +4541,8 @@ main (int argc, char **argv) Display *dpy; Widget toplevel_shell; char *real_progname = argv[0]; - char window_title[255]; + char *window_title; + char *geom = 0; Bool crapplet_p = False; char *str; @@ -3845,35 +4607,86 @@ main (int argc, char **argv) /* 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 (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--; + 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); + 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--; + } + 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. */ @@ -3996,7 +4809,8 @@ main (int argc, char **argv) ; else { - fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, argv[i]); + fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, + argv[i]); fprintf (stderr, "%s: %s\n", real_progname, usage); exit (1); } @@ -4008,7 +4822,11 @@ main (int argc, char **argv) was in argv[0]. */ p->db = db; - load_init_file (p); + s->nscreens = screen_count (dpy); + + hack_environment (s); /* must be before initialize_sort_map() */ + + load_init_file (dpy, p); initialize_sort_map (s); /* Now that Xt has been initialized, and the resources have been read, @@ -4056,6 +4874,7 @@ main (int argc, char **argv) /* Set the main window's title. */ { + char *base_title = _("Screensaver Preferences"); char *v = (char *) strdup(strchr(screensaver_id, ' ')); char *s1, *s2, *s3, *s4; s1 = (char *) strchr(v, ' '); s1++; @@ -4064,7 +4883,12 @@ main (int argc, char **argv) s4 = (char *) strchr(s3, ')'); *s2 = 0; *s4 = 0; - sprintf (window_title, "%.50s %.50s, %.50s", progclass, s1, s3); + + 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); @@ -4124,10 +4948,26 @@ main (int argc, char **argv) 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); + int i; + for (i = 0; kids; kids = kids->next, i++) + { + gtk_signal_connect (GTK_OBJECT (kids->data), "activate", + GTK_SIGNAL_FUNC (mode_menu_item_cb), + (gpointer) s); + + /* The "random-same" mode menu item does not appear unless + there are multple screens. + */ + if (s->nscreens <= 1 && + mode_menu_order[i] == RANDOM_HACKS_SAME) + gtk_widget_hide (GTK_WIDGET (kids->data)); + } + + if (s->nscreens <= 1) /* recompute option-menu size */ + { + gtk_widget_unrealize (GTK_WIDGET (menu)); + gtk_widget_realize (GTK_WIDGET (menu)); + } } @@ -4153,7 +4993,6 @@ main (int argc, char **argv) # 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")); @@ -4164,7 +5003,7 @@ main (int argc, char **argv) gtk_widget_ref (outer_vbox); gtk_container_remove (GTK_CONTAINER (s->toplevel_widget), outer_vbox); - GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING); + 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. */ @@ -4186,9 +5025,27 @@ main (int argc, char **argv) # 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' */ - hack_environment (s); fix_preview_visual (s); /* Realize page zero, so that we can diddle the scrollbar when the @@ -4206,7 +5063,8 @@ main (int argc, char **argv) /* Issue any warnings about the running xscreensaver daemon. */ - the_network_is_not_the_computer (s); + if (! s->debug_p) + the_network_is_not_the_computer (s); /* Run the Gtk event loop, and not the Xt event loop. This means that @@ -4224,17 +5082,18 @@ main (int argc, char **argv) gtk_timeout_add (500, delayed_scroll_kludge, s); -#if 0 +#if 1 /* 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[s->hack_number_to_list_elt[i]]; - conf_data *d = load_configurator (hack->command, False); - if (d) free_conf_data (d); - } - } + if (s->debug_p) + { + int i; + for (i = 0; i < p->screenhacks_count; i++) + { + screenhack *hack = p->screenhacks[i]; + conf_data *d = load_configurator (hack->command, s->debug_p); + if (d) free_conf_data (d); + } + } #endif @@ -4245,7 +5104,7 @@ main (int argc, char **argv) # endif /* HAVE_CRAPPLET */ gtk_main (); - kill_preview_subproc (s); + kill_preview_subproc (s, False); exit (0); }