diff options
Diffstat (limited to 'driver/demo-Gtk.c')
-rw-r--r-- | driver/demo-Gtk.c | 6100 |
1 files changed, 3078 insertions, 3022 deletions
diff --git a/driver/demo-Gtk.c b/driver/demo-Gtk.c index b5e82e2..4950369 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 © 1993-2021 Jamie Zawinski <jwz@jwz.org> + * xscreensaver, Copyright © 1993-2024 Jamie Zawinski <jwz@jwz.org> * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,23 +16,6 @@ #ifdef HAVE_GTK /* whole file */ -#include "blurb.h" - -#include <xscreensaver-intl.h> - -#include <stdlib.h> - -#ifdef HAVE_UNISTD_H -# include <unistd.h> -#endif - -# ifdef __GNUC__ -# define STFU __extension__ /* ignore gcc -pendantic warnings in next sexp */ -# else -# define STFU /* */ -# endif - - #ifdef ENABLE_NLS # include <locale.h> #endif /* ENABLE_NLS */ @@ -42,32 +25,22 @@ #endif /* HAVE_UNAME */ #include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> #include <time.h> #include <pwd.h> /* for getpwuid() */ #include <sys/stat.h> #include <sys/time.h> - #include <signal.h> #include <errno.h> #ifdef HAVE_SYS_WAIT_H # include <sys/wait.h> /* for waitpid() and associated macros */ #endif - -#include <X11/Xproto.h> /* for CARD32 */ #include <X11/Xatom.h> /* for XA_INTEGER */ -#include <X11/Intrinsic.h> -#include <X11/StringDefs.h> - -/* We don't actually use any widget internals, but these are included - so that gdb will have debug info for the widgets... */ -#include <X11/IntrinsicP.h> -#include <X11/ShellP.h> - -#ifdef HAVE_XINERAMA -# include <X11/extensions/Xinerama.h> -#endif /* HAVE_XINERAMA */ +#include <X11/Shell.h> #if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */ # undef inline @@ -76,130 +49,78 @@ # pragma GCC diagnostic ignored "-Wlong-long" # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wpedantic" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include <gtk/gtk.h> - -#ifdef HAVE_CRAPPLET -# include <gnome.h> -# include <capplet-widget.h> -#endif - -#include <gdk/gdkx.h> - -#ifdef HAVE_GTK2 -# include <gmodule.h> -#else /* !HAVE_GTK2 */ -# define G_MODULE_EXPORT /**/ -#endif /* !HAVE_GTK2 */ - -#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 <gdk/gdkx.h> /* For gdk_x11_get_default_xdisplay(), etc. */ #if (__GNUC__ >= 4) # pragma GCC diagnostic pop #endif - +#include "blurb.h" +#include "xscreensaver-intl.h" #include "version.h" #include "types.h" #include "resources.h" /* for parse_time() */ #include "remote.h" /* for xscreensaver_command() */ +#include "screens.h" #include "visual.h" #include "atoms.h" #include "usleep.h" +#include "atoms.h" +#include "screenshot.h" #include "xmu.h" -#include "logo-50.xpm" -#include "logo-180.xpm" - #include "demo-Gtk-conf.h" -#include "atoms.h" - -#include <stdio.h> -#include <string.h> -#include <ctype.h> - -#ifdef HAVE_GTK2 -enum { - COL_ENABLED, - COL_NAME, - COL_LAST -}; -#endif /* HAVE_GTK2 */ -/* Deal with deprecation of direct access to struct fields on the way to GTK3 - See http://live.gnome.org/GnomeGoals/UseGseal - */ -#if GTK_CHECK_VERSION(2,14,0) -# define GET_PARENT(w) gtk_widget_get_parent (w) -# define GET_WINDOW(w) gtk_widget_get_window (w) -# define GET_ACTION_AREA(d) gtk_dialog_get_action_area (d) -# define GET_CONTENT_AREA(d) gtk_dialog_get_content_area (d) -# define GET_ADJ_VALUE(a) gtk_adjustment_get_value (a) -# define SET_ADJ_VALUE(a,v) gtk_adjustment_set_value (a, v) -# define SET_ADJ_UPPER(a,v) gtk_adjustment_set_upper (a, v) -#else -# define GET_PARENT(w) ((w)->parent) -# define GET_WINDOW(w) ((w)->window) -# define GET_ACTION_AREA(d) ((d)->action_area) -# define GET_CONTENT_AREA(d) ((d)->vbox) -# define GET_ADJ_VALUE(a) ((a)->value) -# define SET_ADJ_VALUE(a,v) (a)->value = v -# define SET_ADJ_UPPER(a,v) (a)->upper = v -#endif - -#if GTK_CHECK_VERSION(2,18,0) -# define SET_CAN_DEFAULT(w) gtk_widget_set_can_default ((w), TRUE) -# define GET_SENSITIVE(w) gtk_widget_get_sensitive (w) -#else -# define SET_CAN_DEFAULT(w) GTK_WIDGET_SET_FLAGS ((w), GTK_CAN_DEFAULT) -# define GET_SENSITIVE(w) GTK_WIDGET_IS_SENSITIVE (w) -#endif - -#if GTK_CHECK_VERSION(2,20,0) -# define GET_REALIZED(w) gtk_widget_get_realized (w) -#else -# define GET_REALIZED(w) GTK_WIDGET_REALIZED (w) -#endif /* 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))) +const char *progclass = "XScreenSaver"; -char *progclass = "XScreenSaver"; -XrmDatabase db; +#ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the + length ISO C89 compilers are required to support". */ +#endif +static char *defaults[] = { +#include "XScreenSaver_ad.h" + 0 +}; /* The order of the items in the mode menu. */ static int mode_menu_order[] = { DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME }; +enum { COL_ENABLED, COL_NAME, COL_LAST }; +typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button; typedef struct { char *short_version; /* version number of this xscreensaver build */ - GtkWidget *toplevel_widget; /* the main window */ - GtkWidget *base_widget; /* root of our hierarchy (for name lookups) */ - GtkWidget *popup_widget; /* the "Settings" dialog */ - conf_data *cdata; /* private data for per-hack configuration */ + GtkWindow *window; + GtkWindow *dialog; + + Display *dpy; + Bool wayland_p; + Pixmap screenshot; + Visual *gl_visual; -#ifdef HAVE_GTK2 - GtkBuilder *gtk_ui; /* UI file */ -#endif /* HAVE_GTK2 */ + conf_data *cdata; /* private data for per-hack configuration */ Bool debug_p; /* whether to print diagnostics */ Bool initializing_p; /* flag for breaking recursion loops */ + Bool flushing_p; /* flag for breaking recursion loops */ Bool saving_p; /* flag for breaking recursion loops */ + Bool dpms_supported_p; /* Whether XDPMS is available */ char *desired_preview_cmd; /* subprocess we intend to run */ char *running_preview_cmd; /* subprocess we are currently running */ @@ -222,16 +143,149 @@ typedef struct { int _selected_list_element; /* don't use this: call selected_list_element() instead */ - int nscreens; /* How many X or Xinerama screens there are */ + Bool multi_screen_p; /* Is there more than one monitor */ saver_preferences prefs; } state; -/* Total fucking evilness due to the fact that it's rocket science to get - a closure object of our own down into the various widget callbacks. */ -static state *global_state_kludge; +/* Class definitions for the application and two windows. The classes are: + + XScreenSaverApp -- The invisible GtkApplication main-loop framework. + XScreenSaverWindow -- The main window with the scrolling list of hacks. + XScreenSaverDialog -- The per-hack settings window. + */ +#define XSCREENSAVER_APP_TYPE (xscreensaver_app_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverApp, xscreensaver_app, XSCREENSAVER, APP, + GtkApplication) + +struct _XScreenSaverApp { + GtkApplication parent; + Bool cmdline_debug_p; +}; + + +G_DEFINE_TYPE (XScreenSaverApp, xscreensaver_app, GTK_TYPE_APPLICATION) + +/* The widgets we reference from the demo.ui file. + */ +#define ALL_WINDOW_WIDGETS \ + W(activate_menuitem) \ + W(lock_menuitem) \ + W(kill_menuitem) \ + W(restart_menuitem) \ + W(list) \ + W(scroller) \ + W(preview_frame) \ + W(short_preview_label) \ + W(preview_author_label) \ + W(timeout_spinbutton) \ + W(cycle_spinbutton) \ + W(lock_spinbutton) \ + W(dpms_standby_spinbutton) \ + W(dpms_suspend_spinbutton) \ + W(dpms_off_spinbutton) \ + W(fade_spinbutton) \ + W(lock_button) \ + W(dpms_button) \ + W(dpms_quickoff_button) \ + W(grab_desk_button) \ + W(grab_video_button) \ + W(grab_image_button) \ + W(fade_button) \ + W(unfade_button) \ + W(preview) \ + W(preview_notebook) \ + W(text_radio) \ + W(text_file_radio) \ + W(text_file_browse) \ + W(text_program_radio) \ + W(text_url_radio) \ + W(text_host_radio) \ + W(image_text) \ + W(image_browse_button) \ + W(text_entry) \ + W(text_file_entry) \ + W(text_program_entry) \ + W(text_url_entry) \ + W(text_program_browse) \ + W(theme_menu) \ + W(mode_menu) \ + W(next_prev_hbox) \ + W(blanking_table) \ + W(lock_mlabel) \ + W(dpms_frame) \ + W(dpms_standby_label) \ + W(dpms_standby_mlabel) \ + W(dpms_suspend_label) \ + W(dpms_suspend_mlabel) \ + W(dpms_off_label) \ + W(dpms_off_mlabel) \ + W(fade_label) \ + W(demo) \ + W(settings) \ + +/* The widgets we reference from the prefs.ui file. + */ +#define ALL_DIALOG_WIDGETS \ + W(opt_notebook) \ + W(doc) \ + W(settings_vbox) \ + W(cmd_text) \ + W(opt_frame) \ + W(dialog_vbox) \ + W(adv_button) \ + W(std_button) \ + W(cmd_label) \ + W(manual) \ + W(visual) \ + W(visual_combo) \ + W(reset_button) \ + W(ok_button) \ + +#define XSCREENSAVER_WINDOW_TYPE (xscreensaver_window_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverWindow, xscreensaver_window, + XSCREENSAVER, WINDOW, GtkApplicationWindow) + +struct _XScreenSaverWindow { + GtkApplicationWindow parent; + state state; + + GtkWidget +# undef W +# define W(NAME) * NAME, + ALL_WINDOW_WIDGETS + *_dummy; +# undef W +}; + +G_DEFINE_TYPE (XScreenSaverWindow, xscreensaver_window, + GTK_TYPE_APPLICATION_WINDOW) + + +#define XSCREENSAVER_DIALOG_TYPE (xscreensaver_dialog_get_type()) +G_DECLARE_FINAL_TYPE (XScreenSaverDialog, xscreensaver_dialog, + XSCREENSAVER, DIALOG, GtkDialog) + +struct _XScreenSaverDialog { + GtkApplicationWindow parent; + XScreenSaverWindow *main; + char *unedited_cmdline; /* Current hack command line before saving */ + + GtkWidget +# undef W +# define W(NAME) * NAME, + ALL_DIALOG_WIDGETS + *_dummy; +# undef W +}; + +G_DEFINE_TYPE (XScreenSaverDialog, xscreensaver_dialog, + GTK_TYPE_DIALOG) + + +static void hack_subproc_environment (Window preview_window_id, Bool debug_p); static void populate_demo_window (state *, int list_elt); static void populate_prefs_page (state *); @@ -239,418 +293,379 @@ static void populate_popup_window (state *); static Bool flush_dialog_changes_and_save (state *); static Bool flush_popup_changes_and_save (state *); +static Bool validate_image_directory (state *, const char *path); 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 *, Bool reset_p); static void schedule_preview_check (state *); +static void sensitize_demo_widgets (state *, Bool sensitive_p); +static void kill_gnome_screensaver (state *); +static void kill_kde_screensaver (state *); - -/* Prototypes of functions used by the Gtk-generated code, to avoid warnings. - */ -void exit_menu_cb (GtkAction *, gpointer user_data); -void about_menu_cb (GtkAction *, gpointer user_data); -void doc_menu_cb (GtkAction *, gpointer user_data); -void file_menu_cb (GtkAction *, gpointer user_data); -void activate_menu_cb (GtkAction *, gpointer user_data); -void lock_menu_cb (GtkAction *, gpointer user_data); -void kill_menu_cb (GtkAction *, 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_reset_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); -void preview_theme_cb (GtkWidget *, gpointer user_data); - -static void kill_gnome_screensaver (void); -static void kill_kde_screensaver (void); +/* Some pathname utilities */ -/* Some random utility functions +/* Removed redundant . and .. components from the pathname. + Strip leading and trailing spaces. + Make it have a trailing slash if it should be a directory. */ - -static GtkWidget * -name_to_widget (state *s, const char *name) +static char * +normalize_pathname (const char *path, gboolean dir_p) { - GtkWidget *w; - if (!s) abort(); - if (!name) abort(); - if (!*name) abort(); + int L; + char *p2, *s; + if (!path) return 0; + if (!*path) return strdup (""); -#ifdef HAVE_GTK2 - if (!s->gtk_ui) - { - /* First try to load the UI file from the current directory; - if there isn't one there, check the installed directory. - */ -# define UI_FILE "xscreensaver.ui" - const char * const files[] = { UI_FILE, - DEFAULT_ICONDIR "/" UI_FILE }; - int i; + /* Strip leading spaces */ + while (isspace (*path)) path++; - s->gtk_ui = gtk_builder_new (); + L = strlen (path); + p2 = (char *) malloc (L + 3); + strcpy (p2, path); - for (i = 0; i < countof (files); i++) + /* Strip trailing spaces and slashes */ + while (L > 0 && (isspace (p2[L-1]) || p2[L-1] == '/')) + p2[--L] = 0; + + for (s = p2; s && *s; s++) + { + if (*s == '/' && + (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */ + !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */ { - struct stat st; - if (!stat (files[i], &st)) + char *s0 = s; + while (s0 > p2 && s0[-1] != '/') + s0--; + if (s0 > p2) { - GError* error = NULL; - - if (gtk_builder_add_from_file (s->gtk_ui, files[i], &error)) - break; - else - { - g_warning ("Couldn't load builder file %s: %s", - files[i], error->message); - g_error_free (error); - } + s0--; + s += 3; + /* strcpy (s0, s); */ + memmove(s0, s, strlen(s) + 1); + s = s0-1; } } - if (i >= countof (files)) - { - fprintf (stderr, - "%s: could not load \"" UI_FILE "\"\n" - "\tfrom " DEFAULT_ICONDIR "/ or current directory.\n", - blurb()); - exit (-1); - } -# undef UI_FILE - - gtk_builder_connect_signals (s->gtk_ui, NULL); + else if (*s == '/' && !strncmp (s, "/./", 3)) { /* delete "/./" */ + /* strcpy (s, s+2), s--; */ + memmove(s, s+2, strlen(s+2) + 1); + s--; + } + else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */ + *s = 0, s--; } - w = GTK_WIDGET (gtk_builder_get_object (s->gtk_ui, name)); - -#else /* !HAVE_GTK2 */ - - w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget), - name); - if (w) return w; - w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget), - name); -#endif /* HAVE_GTK2 */ - if (w) return w; - - fprintf (stderr, "%s: no widget \"%s\" (wrong UI file?)\n", - blurb(), name); - abort(); -} - - -/* Why this behavior isn't automatic in *either* toolkit, I'll never know. - Takes a scroller, viewport, or list as an argument. - */ -static void -ensure_selected_item_visible (GtkWidget *widget) -{ -#ifdef HAVE_GTK2 - GtkTreePath *path; - GtkTreeSelection *selection; - GtkTreeIter iter; - GtkTreeModel *model; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); - if (!gtk_tree_selection_get_selected (selection, &model, &iter)) - path = gtk_tree_path_new_first (); - else - path = gtk_tree_model_get_path (model, &iter); - - gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE); - - gtk_tree_path_free (path); + /* + Normalize consecutive slashes. + Ignore doubled slashes after ":" to avoid mangling URLs. + */ -#else /* !HAVE_GTK2 */ + for (s = p2; s && *s; s++){ + if (*s == ':') continue; + if (!s[1] || !s[2]) continue; + while (s[1] == '/' && s[2] == '/') + /* strcpy (s+1, s+2); */ + memmove (s+1, s+2, strlen(s+2) + 1); + } - GtkScrolledWindow *scroller = 0; - GtkViewport *vp = 0; - GtkList *list_widget = 0; - GList *slist; - GList *kids; - int nkids = 0; - GtkWidget *selected = 0; - int list_elt = -1; - GtkAdjustment *adj; - gint parent_h, child_y, child_h, children_h, ignore; - double ratio_t, ratio_b; + /* and strip trailing whitespace for good measure. */ + L = strlen(p2); + while (isspace(p2[L-1])) + p2[--L] = 0; - if (GTK_IS_SCROLLED_WINDOW (widget)) - { - scroller = GTK_SCROLLED_WINDOW (widget); - vp = GTK_VIEWPORT (GTK_BIN (scroller)->child); - list_widget = GTK_LIST (GTK_BIN(vp)->child); - } - else if (GTK_IS_VIEWPORT (widget)) - { - vp = GTK_VIEWPORT (widget); - scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); - list_widget = GTK_LIST (GTK_BIN(vp)->child); - } - else if (GTK_IS_LIST (widget)) + if (dir_p) { - list_widget = GTK_LIST (widget); - vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent); - scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); + p2[L++] = '/'; /* Add trailing slash */ + p2[L] = 0; } - else - abort(); - - slist = list_widget->selection; - selected = (slist ? GTK_WIDGET (slist->data) : 0); - if (!selected) - return; - list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected)); + return p2; +} - for (kids = gtk_container_children (GTK_CONTAINER (list_widget)); - kids; kids = kids->next) - nkids++; - adj = gtk_scrolled_window_get_vadjustment (scroller); - - gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (vp)), - &ignore, &ignore, &ignore, &parent_h, &ignore); - gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (selected)), - &ignore, &child_y, &ignore, &child_h, &ignore); - children_h = nkids * child_h; +/* Expand or un-expand ~/ to $HOME in a pathname, as requested. + Strip leading and trailing spaces. + Make it have a trailing slash if it should be a directory. + */ +static char * +pathname_tilde (const char *p, gboolean add_p, gboolean dir_p) +{ + char *p2; + if (!p) return 0; - ratio_t = ((double) child_y) / ((double) children_h); - ratio_b = ((double) child_y + child_h) / ((double) children_h); + if (dir_p && + (!strncasecmp (p, "http://", 7) || + !strncasecmp (p, "https://", 8))) + dir_p = FALSE; - if (adj->upper == 0.0) /* no items in list */ - return; + p2 = normalize_pathname (p, dir_p); + p = p2; - if (ratio_t < (adj->value / adj->upper) || - ratio_b > ((adj->value + adj->page_size) / adj->upper)) + if (add_p) { - double target; - int slop = parent_h * 0.75; /* how much to overshoot by */ - - if (ratio_t < (adj->value / adj->upper)) + const char *home = getenv("HOME"); + int L = strlen(home); + if (!strncmp (home, p, L) && p[L] == '/') { - double ratio_w = ((double) parent_h) / ((double) children_h); - double ratio_l = (ratio_b - ratio_t); - target = ((ratio_t - ratio_w + ratio_l) * adj->upper); - target += slop; + char *p3 = (char *) malloc (strlen (p) + 4); + strcpy (p3, "~"); + strcat (p3, p + L); + free (p2); + p2 = p3; } - else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/ - { - target = ratio_t * adj->upper; - target -= slop; - } - - if (target > adj->upper - adj->page_size) - target = adj->upper - adj->page_size; - if (target < 0) - target = 0; - - gtk_adjustment_set_value (adj, target); } -#endif /* !HAVE_GTK2 */ -} + else if (!strncmp (p, "~/", 2)) + { + const char *home = getenv("HOME"); + char *p3 = (char *) malloc (strlen (p) + strlen (home) + 4); + strcpy (p3, home); + strcat (p3, p + 1); + free (p2); + p2 = p3; + } -static void -warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data) -{ - GtkWidget *shell = GTK_WIDGET (user_data); - while (GET_PARENT (shell)) - shell = GET_PARENT (shell); - gtk_widget_destroy (GTK_WIDGET (shell)); + return p2; } -void restart_menu_cb (GtkWidget *widget, gpointer user_data); - -static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data) +/* Is the path a directory that exists? */ +static gboolean +directory_p (const char *path) { - restart_menu_cb (widget, user_data); - warning_dialog_dismiss_cb (widget, user_data); -} + char *p2 = pathname_tilde (path, FALSE, FALSE); /* no slash on dir */ + struct stat st; + gboolean ok = FALSE; -static void warning_dialog_killg_cb (GtkWidget *widget, gpointer user_data) -{ - kill_gnome_screensaver (); - warning_dialog_dismiss_cb (widget, user_data); + if (!p2 || !*p2) + ok = FALSE; + else if (stat (p2, &st)) + ok = FALSE; + else if (!S_ISDIR (st.st_mode)) + ok = FALSE; + else + ok = TRUE; + free (p2); + return ok; } -static void warning_dialog_killk_cb (GtkWidget *widget, gpointer user_data) + +/* Is the path a file (not directory) that exists? */ +static gboolean +file_p (const char *path) { - kill_kde_screensaver (); - warning_dialog_dismiss_cb (widget, user_data); + char *p2 = pathname_tilde (path, FALSE, FALSE); + struct stat st; + gboolean ok = FALSE; + if (!p2 || !*p2) + ok = FALSE; + else if (stat (p2, &st)) + ok = FALSE; + else if (S_ISDIR (st.st_mode)) + ok = FALSE; + else + ok = TRUE; + free (p2); + return ok; } -typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button; + +/* See if the directory has at least one image file under it. + Recurses to at most the given depth, chasing symlinks. + To do this properly would mean running "xscreensaver-getimage-file" + and seeing if it found anything, but that might take a long time to + build the cache the first time, so this is close enough. + */ static Bool -warning_dialog (GtkWidget *parent, const char *message, - dialog_button button_type, int center) +image_files_p (const char *path, int max_depth) { - char *msg = strdup (message); - char *head; + const char * const exts[] = { + "jpg", "jpeg", "pjpeg", "pjpg", "png", "gif", + "tif", "tiff", "xbm", "xpm", "svg", + }; + struct dirent *de; + Bool ok = FALSE; + char *p2 = pathname_tilde (path, FALSE, FALSE); /* no slash on dir */ + DIR *dir = opendir (p2); + if (!dir) goto DONE; + + while (!ok && (de = readdir (dir))) + { + struct stat st; + const char *f = de->d_name; + char *f2; + if (*f == '.') continue; - GtkWidget *dialog = gtk_dialog_new (); - GtkWidget *label = 0; - GtkWidget *ok = 0; - GtkWidget *cancel = 0; - int i = 0; + f2 = (char *) malloc (strlen(p2) + strlen(f) + 10); + strcpy (f2, p2); + strcat (f2, "/"); + strcat (f2, f); - while (parent && !GET_WINDOW (parent)) - parent = GET_PARENT (parent); + if (!stat (f2, &st)) + { + if (S_ISDIR (st.st_mode)) + { + if (max_depth > 0 && image_files_p (f2, max_depth - 1)) + ok = TRUE; + } + else + { + int i; + const char *ext = strrchr (f, '.'); + if (ext) + for (i = 0; i < countof(exts); i++) + if (!strcasecmp (ext+1, exts[i])) + { + /* fprintf (stderr, "%s: found %s\n", blurb(), f2); */ + ok = TRUE; + break; + } + } + } - if (!parent || - !GET_WINDOW (parent)) /* too early to pop up transient dialogs */ - { - fprintf (stderr, - "%s: too early for warning dialog?" - "\n\n\t%s\n\n", - progname, message); - free(msg); - return False; + free (f2); } - head = msg; - while (head) - { - char name[20]; - char *s = strchr (head, '\n'); - if (s) *s = 0; + closedir (dir); + DONE: + free (p2); + return ok; +} - sprintf (name, "label%d", i++); - { - label = gtk_label_new (head); -#ifdef HAVE_GTK2 - gtk_label_set_selectable (GTK_LABEL (label), TRUE); -#endif /* HAVE_GTK2 */ +/* Some random utility functions + */ -#ifndef HAVE_GTK2 - if (i == 1) - { - GTK_WIDGET (label)->style = - gtk_style_copy (GTK_WIDGET (label)->style); - GTK_WIDGET (label)->style->font = - gdk_font_load (get_string_resource("warning_dialog.headingFont", - "Dialog.Font")); - gtk_widget_set_style (GTK_WIDGET (label), - GTK_WIDGET (label)->style); - } -#endif /* !HAVE_GTK2 */ - if (center <= 0) - gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))), - label, TRUE, TRUE, 0); - gtk_widget_show (label); - } +/* Why this behavior isn't automatic, I'll never understand. + */ +static void +ensure_selected_item_visible (state *s, GtkWidget *widget) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); - if (s) - head = s+1; - else - head = 0; + /* Find the path of the selected row in the list. + */ + GtkTreeView *list_widget = GTK_TREE_VIEW (win->list); + GtkTreeSelection *selection = gtk_tree_view_get_selection (list_widget); + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; - center--; - } + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + path = gtk_tree_path_new_first (); + else + path = gtk_tree_model_get_path (model, &iter); - label = gtk_label_new (""); - gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))), - label, TRUE, TRUE, 0); - gtk_widget_show (label); + /* Make this item be visible and selected. */ + gtk_tree_view_set_cursor (list_widget, path, NULL, FALSE); - label = gtk_hbutton_box_new (); - gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))), - label, TRUE, TRUE, 0); + /* Make the scroller show that item at the center of the viewport. + The set_cursor() call, above, makes the item be visible, but it hugs + the top or bottom edge of the viewport, instead of providing more + surrounding context. -#ifdef HAVE_GTK2 - if (button_type != D_NONE) + Doing the following in list_select_changed_cb() instead of here makes + the list vertically re-center when using the cursor keys instead of + hugging the top or bottom (good) but also makes it re-center when + clicking on a new item with the mouse (bad). + */ + if (gtk_widget_get_realized (GTK_WIDGET (list_widget))) { - cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL); - gtk_container_add (GTK_CONTAINER (label), cancel); + GdkWindow *bin = gtk_tree_view_get_bin_window (list_widget); + int binh = gdk_window_get_height (bin); + GdkRectangle r; + gtk_tree_view_get_cell_area (list_widget, path, NULL, &r); + gtk_tree_view_convert_widget_to_tree_coords (list_widget, + r.x, r.y, &r.x, &r.y); + gtk_tree_view_scroll_to_point (list_widget, r.x, r.y - binh / 2); } - ok = gtk_button_new_from_stock (GTK_STOCK_OK); - gtk_container_add (GTK_CONTAINER (label), ok); - -#else /* !HAVE_GTK2 */ - - ok = gtk_button_new_with_label ("OK"); - gtk_container_add (GTK_CONTAINER (label), ok); - - if (button_type != D_NONE) - { - cancel = gtk_button_new_with_label ("Cancel"); - gtk_container_add (GTK_CONTAINER (label), cancel); - } + gtk_tree_path_free (path); +} -#endif /* !HAVE_GTK2 */ - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); - gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); - gtk_window_set_title (GTK_WINDOW (dialog), progclass); - SET_CAN_DEFAULT (ok); - gtk_widget_show (ok); - gtk_widget_grab_focus (ok); +static void +warning_dialog_cb (GtkDialog *dialog, gint response_id, gpointer user_data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) + fprintf (stderr, "%s: dialog response %d\n", blurb(), response_id); + switch (response_id) { + case D_LAUNCH: restart_menu_cb (GTK_WIDGET (dialog), user_data); break; + case D_GNOME: kill_gnome_screensaver (s); break; + case D_KDE: kill_kde_screensaver (s); break; + default: /* D_NONE or GTK_RESPONSE_DELETE_EVENT */ + break; + } + gtk_widget_destroy (GTK_WIDGET (dialog)); +} - if (cancel) - { - SET_CAN_DEFAULT (cancel); - gtk_widget_show (cancel); - } - gtk_widget_show (label); - gtk_widget_show (dialog); - if (button_type != D_NONE) - { - GtkSignalFunc fn; - switch (button_type) { - case D_LAUNCH: fn = GTK_SIGNAL_FUNC (warning_dialog_restart_cb); break; - case D_GNOME: fn = GTK_SIGNAL_FUNC (warning_dialog_killg_cb); break; - case D_KDE: fn = GTK_SIGNAL_FUNC (warning_dialog_killk_cb); break; - default: abort(); break; - } - gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", fn, - (gpointer) dialog); - gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked", - GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), - (gpointer) dialog); - } - else - { - gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", - GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), - (gpointer) dialog); - } +static Bool +warning_dialog_1 (GtkWindow *win, + const char *title, + const char *message, + dialog_button button_type) +{ + GtkWidget *dialog = + (button_type == D_NONE + ? gtk_dialog_new_with_buttons (title, win, + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_OK"), D_NONE, + NULL) + : gtk_dialog_new_with_buttons (title, win, + GTK_DIALOG_DESTROY_WITH_PARENT, + (button_type == D_LAUNCH ? _("Launch") : + button_type == D_GNOME ? _("Kill") : + button_type == D_KDE ? _("Kill") : + _("_OK")), + button_type, + _("_Cancel"), D_NONE, + NULL)); + GtkWidget *content_area = + gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + GtkWidget *im = gtk_image_new_from_icon_name ("dialog-warning", + GTK_ICON_SIZE_DIALOG); + GtkWidget *label = gtk_label_new (message); + int margin = 32; + + gtk_box_pack_start (GTK_BOX (hbox), im, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (content_area), hbox); + + gtk_widget_set_margin_start (hbox, margin); + gtk_widget_set_margin_end (hbox, margin); + gtk_widget_set_margin_top (hbox, margin); + gtk_widget_set_margin_bottom (hbox, margin / 2); + + gtk_widget_set_margin_start (label, margin / 2); + gtk_widget_set_valign (im, GTK_ALIGN_START); + + g_signal_connect (dialog, "response", + G_CALLBACK (warning_dialog_cb), + win); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), button_type); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_transient_for (GTK_WINDOW (dialog), win); + gtk_widget_show_all (dialog); - gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)), - GET_WINDOW (GTK_WIDGET (parent))); + return TRUE; +} -#ifdef HAVE_GTK2 - gtk_window_present (GTK_WINDOW (dialog)); -#else /* !HAVE_GTK2 */ - gdk_window_show (GTK_WIDGET (dialog)->window); - gdk_window_raise (GTK_WIDGET (dialog)->window); -#endif /* !HAVE_GTK2 */ - free (msg); - return True; +void +warning_dialog (GtkWindow *win, const char *title, const char *message) +{ + warning_dialog_1 (win, title, message, D_NONE); } @@ -660,8 +675,13 @@ run_cmd (state *s, Atom command, int arg) char *err = 0; int status; + if (!s->dpy) return; flush_dialog_changes_and_save (s); - status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err); + + if (s->debug_p) + fprintf (stderr, "%s: command: %s %d\n", blurb(), + XGetAtomName (s->dpy, command), arg); + status = xscreensaver_command (s->dpy, command, arg, FALSE, &err); /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */ if (status < 0 && err && strstr (err, "unexpectedly deleted")) @@ -670,16 +690,12 @@ run_cmd (state *s, Atom command, int arg) 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, D_NONE, 100); + sprintf (buf, "%.100s", (err ? err : _("Unknown error!"))); + warning_dialog (s->window, _("Error"), buf); } if (err) free (err); - sensitize_menu_items (s, True); - force_dialog_repaint (s); + sensitize_menu_items (s, TRUE); } @@ -690,14 +706,17 @@ run_hack (state *s, int list_elt, Bool report_errors_p) char *err = 0; int status; + if (!s->dpy) return; 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); - status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1, - False, &err); + if (s->debug_p) + fprintf (stderr, "%s: command: DEMO %d\n", blurb(), hack_number + 1); + status = xscreensaver_command (s->dpy, XA_DEMO, hack_number + 1, + FALSE, &err); if (status < 0 && report_errors_p) { @@ -711,11 +730,8 @@ run_hack (state *s, int list_elt, Bool report_errors_p) 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, D_NONE, 100); + sprintf (buf, "%.100s", err ? err : _("Unknown error!")); + warning_dialog (s->window, _("Error"), buf); } } else @@ -723,283 +739,232 @@ run_hack (state *s, int list_elt, Bool report_errors_p) /* The error is that the daemon isn't running; offer to restart it. */ - const char *d = DisplayString (GDK_DISPLAY()); + const char *d = DisplayString (s->dpy); char msg [1024]; sprintf (msg, - _("Warning:\n\n" - "The XScreenSaver daemon doesn't seem to be running\n" - "on display \"%s\". Launch it now?"), + _("The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%.25s\". Launch it now?"), d); - warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + warning_dialog_1 (s->window, _("Warning"), msg, D_LAUNCH); } } if (err) free (err); - sensitize_menu_items (s, False); + sensitize_menu_items (s, FALSE); } - -/* Button callbacks +static pid_t +fork_and_exec (state *s, int argc, char **argv) +{ + char buf [255]; + pid_t forked = fork(); + switch ((int) forked) { + case -1: + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + + case 0: + if (s->dpy) close (ConnectionNumber (s->dpy)); + execvp (argv[0], argv); /* shouldn't return. */ + + sprintf (buf, "%s: pid %lu: couldn't exec %s", blurb(), + (unsigned long) getpid(), argv[0]); + perror (buf); + exit (1); /* exits child fork */ + break; + + default: /* parent fork */ + + /* Put it in its own process group so that this process getting SIGTERM + does not propagate to the forked process. */ + if (setpgid (forked, 0)) + { + char buf [255]; + sprintf (buf, "%s: setpgid %d", blurb(), forked); + perror (buf); + } + + if (s->debug_p) + { + int i; + fprintf (stderr, "%s: pid %lu: forked:", blurb(), + (unsigned long) forked); + for (i = 0; i < argc; i++) + if (strchr (argv[i], ' ')) + fprintf (stderr, " \"%s\"", argv[i]); + else + fprintf (stderr, " %s", argv[i]); + fprintf (stderr, "\n"); + } - According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make - GTK work on Cygwin; apparently all GTK callbacks need this magic extra - declaration. I do not pretend to understand. - */ + break; + } -G_MODULE_EXPORT void -exit_menu_cb (GtkAction *menu_action, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - flush_dialog_changes_and_save (s); - kill_preview_subproc (s, False); - gtk_main_quit (); + return forked; } -static gboolean -wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) + + +/**************************************************************************** + + XScreenSaverWindow callbacks, referenced by demo.ui. + + ****************************************************************************/ + +/* File menu / Quit */ +G_MODULE_EXPORT void +quit_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = (state *) data; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: quit menu\n", blurb()); flush_dialog_changes_and_save (s); - gtk_main_quit (); - return TRUE; + kill_preview_subproc (s, FALSE); + g_application_quit (G_APPLICATION ( + gtk_window_get_application (GTK_WINDOW (win)))); } +/* Help menu / About */ G_MODULE_EXPORT void about_menu_cb (GtkAction *menu_action, gpointer user_data) { -#if 1 - /* Let's just pop up the splash dialog instead. */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: about menu\n", blurb()); preview_theme_cb (NULL, user_data); -#else - char msg [2048]; - char copy[1024]; - char *desc = _("For updates, check https://www.jwz.org/xscreensaver/"); - - char *version = strdup (screensaver_id + 17); - char *year = strchr (version, '-'); - char *s = strchr (version, ' '); - *s = 0; - year = strchr (year+1, '-') + 1; - s = strchr (year, ')'); - *s = 0; - - /* Ole Laursen <olau@hardworking.dk> 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-%s %s"), year, s); -#else /* !HAVE_GTK2 */ - sprintf(copy, ("Copyright \251 1991-%s %s"), year, s); -#endif /* !HAVE_GTK2 */ - - sprintf (msg, "%s\n\n%s", copy, desc); - - /* I can't make gnome_about_new() work here -- it starts dying in - gdk_imlib_get_visual() under gnome_about_new(). If this worked, - then this might be the thing to do: - - #ifdef HAVE_CRAPPLET - { - const gchar *auth[] = { 0 }; - GtkWidget *about = gnome_about_new (progclass, version, "", auth, desc, - "xscreensaver.xpm"); - gtk_widget_show (about); - } - #else / * GTK but not GNOME * / - ... - */ - { - GdkColormap *colormap; - GdkPixmap *gdkpixmap; - GdkBitmap *mask; - - GtkWidget *dialog = gtk_dialog_new (); - GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok; - GSList *proxies = gtk_action_get_proxies (menu_action); - GtkWidget *parent = GTK_WIDGET (proxies->data); - while (GET_PARENT (parent)) - parent = GET_PARENT (parent); - - hbox = gtk_hbox_new (FALSE, 20); - gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))), - hbox, TRUE, TRUE, 0); - - colormap = gtk_widget_get_colormap (parent); - gdkpixmap = - gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL, - (gchar **) logo_180_xpm); - icon = gtk_pixmap_new (gdkpixmap, mask); - gtk_misc_set_padding (GTK_MISC (icon), 10, 10); - - gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0); - - vbox = gtk_vbox_new (FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); - - label1 = gtk_label_new (version); - gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0); - gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT); - gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75); - -#ifndef HAVE_GTK2 - GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style); - GTK_WIDGET (label1)->style->font = - gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font")); - gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style); -#endif /* HAVE_GTK2 */ - - label2 = gtk_label_new (msg); - gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0); - gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT); - gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25); - -#ifndef HAVE_GTK2 - GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style); - GTK_WIDGET (label2)->style->font = - gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font")); - gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style); -#endif /* HAVE_GTK2 */ - - hb = gtk_hbutton_box_new (); - - gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))), - hb, TRUE, TRUE, 0); - -#ifdef HAVE_GTK2 - ok = gtk_button_new_from_stock (GTK_STOCK_OK); -#else /* !HAVE_GTK2 */ - ok = gtk_button_new_with_label (_("OK")); -#endif /* !HAVE_GTK2 */ - gtk_container_add (GTK_CONTAINER (hb), ok); - - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); - gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); - gtk_window_set_title (GTK_WINDOW (dialog), progclass); - - gtk_widget_show (hbox); - gtk_widget_show (icon); - gtk_widget_show (vbox); - gtk_widget_show (label1); - gtk_widget_show (label2); - gtk_widget_show (hb); - gtk_widget_show (ok); - gtk_widget_show (dialog); - - gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", - GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb), - (gpointer) dialog); - gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)), - GET_WINDOW (GTK_WIDGET (parent))); - gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog))); - gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog))); - } -#endif /* 0 */ } +/* Help menu / Documentation */ G_MODULE_EXPORT void doc_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; saver_preferences *p = &s->prefs; char *help_command; + int ac = 0; + char *av[10]; + + if (s->debug_p) fprintf (stderr, "%s: doc menu\n", blurb()); if (!p->help_url || !*p->help_url) { - warning_dialog (s->toplevel_widget, - _("Error:\n\n" - "No Help URL has been specified.\n"), D_NONE, 100); + warning_dialog (GTK_WINDOW (win), _("Error"), + _("No Help URL has been specified.\n")); return; } help_command = (char *) malloc (strlen (p->load_url_command) + (strlen (p->help_url) * 5) + 20); - strcpy (help_command, "( "); - sprintf (help_command + strlen(help_command), - p->load_url_command, + sprintf (help_command, p->load_url_command, p->help_url, p->help_url, p->help_url, p->help_url, p->help_url); - strcat (help_command, " ) &"); - if (system (help_command) < 0) - fprintf (stderr, "%s: fork error\n", blurb()); + + av[ac++] = "/bin/sh"; + av[ac++] = "-c"; + av[ac++] = help_command; + av[ac] = 0; + fork_and_exec (s, ac, av); free (help_command); } +/* File menu opened */ G_MODULE_EXPORT void file_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - sensitize_menu_items (s, False); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: file menu post\n", blurb()); + sensitize_menu_items (s, FALSE); } +/* File menu / Activate */ G_MODULE_EXPORT void activate_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: activate menu\n", blurb()); run_cmd (s, XA_ACTIVATE, 0); } +/* File menu / Lock */ G_MODULE_EXPORT void lock_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: lock menu\n", blurb()); run_cmd (s, XA_LOCK, 0); } +/* File menu / Kill daemon */ G_MODULE_EXPORT void kill_menu_cb (GtkAction *menu_action, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: kill menu\n", blurb()); run_cmd (s, XA_EXIT, 0); } +/* File menu / Restart */ G_MODULE_EXPORT void restart_menu_cb (GtkWidget *widget, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + int ac = 0; + char *av[10]; + if (s->debug_p) fprintf (stderr, "%s: restart menu\n", blurb()); + if (!s->dpy) return; flush_dialog_changes_and_save (s); - xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL); + xscreensaver_command (s->dpy, XA_EXIT, 0, FALSE, NULL); sleep (1); - if (system ("xscreensaver -splash &") < 0) - fprintf (stderr, "%s: fork error\n", blurb()); + av[ac++] = "xscreensaver"; + av[ac++] = "--splash"; + if (s->debug_p) av[ac++] = "--verbose"; + av[ac] = 0; + fork_and_exec (s, ac, av); 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 (!s->dpy) return FALSE; + server_xscreensaver_version (s->dpy, &rversion, 0, 0); if (!rversion) - return False; + return FALSE; free (rversion); - return True; + return TRUE; } static void await_xscreensaver (state *s) { int countdown = 5; - Bool ok = False; + Bool ok = FALSE; while (!ok && (--countdown > 0)) if (xscreensaver_running_p (s)) - ok = True; + ok = TRUE; else sleep (1); /* If it's not there yet, wait a second... */ - sensitize_menu_items (s, True); + sensitize_menu_items (s, TRUE); if (! ok) { @@ -1009,37 +974,19 @@ await_xscreensaver (state *s) Bool root_p = (geteuid () == 0); strcpy (buf, - _("Error:\n\n" - "The xscreensaver daemon did not start up properly.\n" + _("The xscreensaver daemon did not start up properly.\n" "\n")); if (root_p) - 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" - "\n" - " xhost +localhost\n" - "\n" - "and then selecting `File / Restart Daemon'.\n" - "\n" - "Note that turning off access control will allow anyone logged\n" - "on to this machine to access your screen, which might be\n" - "considered a security problem. Please read the xscreensaver\n" - "manual and FAQ for more information.\n" - */ - "\n" - "You shouldn't run X as root. Instead, you should log in as a\n" - "normal user, and `sudo' as necessary.")); + strcat (buf, + _("You are running as root. Don't do that. Instead, you should\n" + "log in as a normal user and use `sudo' as necessary.") + ); else strcat (buf, _("Please check your $PATH and permissions.")); - warning_dialog (s->toplevel_widget, buf, D_NONE, 1); + warning_dialog (s->window, _("Error"), buf); } - - force_dialog_repaint (s); } @@ -1050,12 +997,14 @@ selected_list_element (state *s) } +/* Write the settings to disk; call this only when changes have been made. + */ static int demo_write_init_file (state *s, saver_preferences *p) { - Display *dpy = GDK_DISPLAY(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); - if (!write_init_file (dpy, p, s->short_version, False)) + if (!write_init_file (s->dpy, p, s->short_version, FALSE)) { if (s->debug_p) fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name()); @@ -1065,14 +1014,13 @@ demo_write_init_file (state *s, saver_preferences *p) { const char *f = init_file_name(); if (!f || !*f) - warning_dialog (s->toplevel_widget, - _("Error:\n\nCouldn't determine init file name!\n"), - D_NONE, 100); + warning_dialog (GTK_WINDOW (win), _("Error"), + _("Couldn't determine init file name!\n")); else { char *b = (char *) malloc (strlen(f) + 1024); - sprintf (b, _("Error:\n\nCouldn't write %s\n"), f); - warning_dialog (s->toplevel_widget, b, D_NONE, 100); + sprintf (b, _("Couldn't write %s\n"), f); + warning_dialog (GTK_WINDOW (win), _("Error"), b); free (b); } return -1; @@ -1080,106 +1028,57 @@ demo_write_init_file (state *s, saver_preferences *p) } +/* The "Preview" button on the main page. */ G_MODULE_EXPORT void run_this_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; int list_elt = selected_list_element (s); if (list_elt < 0) return; - if (!flush_dialog_changes_and_save (s)) - run_hack (s, list_elt, True); -} - - -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; - 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]; - + if (s->debug_p) fprintf (stderr, "%s: preview button\n", blurb()); flush_dialog_changes_and_save (s); - 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) name2 = str+1; - - cmd = get_string_resource (dpy, "manualCommand", "ManualCommand"); - if (cmd) - { - char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); - strcpy (cmd2, "( "); - sprintf (cmd2 + strlen (cmd2), - cmd, - name2, name2, name2, name2); - strcat (cmd2, " ) &"); - if (system (cmd2) < 0) - fprintf (stderr, "%s: fork error\n", blurb()); - free (cmd2); - } - else - { - warning_dialog (GTK_WIDGET (button), - _("Error:\n\nno `manualCommand' resource set."), - D_NONE, 100); - } - - free (oname); + run_hack (s, list_elt, TRUE); } static void force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p) { - GtkWidget *parent = name_to_widget (s, "scroller"); - gboolean was = GET_SENSITIVE (parent); -#ifdef HAVE_GTK2 + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *parent = win->scroller; + gboolean was = gtk_widget_get_sensitive (parent); GtkTreeIter iter; GtkTreeModel *model; GtkTreeSelection *selection; -#endif /* HAVE_GTK2 */ - if (!was) gtk_widget_set_sensitive (parent, True); -#ifdef HAVE_GTK2 + if (!was) gtk_widget_set_sensitive (parent, TRUE); model = gtk_tree_view_get_model (GTK_TREE_VIEW (list)); if (!model) abort(); 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); + if (s->debug_p) + fprintf (stderr, "%s: select list elt %d\n", blurb(), list_elt); } -#else /* !HAVE_GTK2 */ - gtk_list_select_item (GTK_LIST (list), list_elt); -#endif /* !HAVE_GTK2 */ - if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list)); - if (!was) gtk_widget_set_sensitive (parent, False); + if (scroll_p) ensure_selected_item_visible (s, GTK_WIDGET (list)); + if (!was) gtk_widget_set_sensitive (parent, FALSE); } +/* The down arrow */ 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; */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; Bool ops = s->preview_suppressed_p; - - GtkWidget *list_widget = name_to_widget (s, "list"); + GtkWidget *list_widget = win->list; int list_elt = selected_list_element (s); + if (s->debug_p) fprintf (stderr, "%s: down arrow\n", blurb()); + if (list_elt < 0) list_elt = 0; else @@ -1188,27 +1087,30 @@ run_next_cb (GtkButton *button, gpointer user_data) if (list_elt >= s->list_count) list_elt = 0; - s->preview_suppressed_p = True; + s->preview_suppressed_p = TRUE; flush_dialog_changes_and_save (s); - force_list_select_item (s, list_widget, list_elt, True); + force_list_select_item (s, list_widget, list_elt, TRUE); populate_demo_window (s, list_elt); - run_hack (s, list_elt, False); + populate_popup_window (s); + run_hack (s, list_elt, FALSE); s->preview_suppressed_p = ops; } +/* The up arrow */ 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; */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; Bool ops = s->preview_suppressed_p; - - GtkWidget *list_widget = name_to_widget (s, "list"); + GtkWidget *list_widget = win->list; int list_elt = selected_list_element (s); + if (s->debug_p) fprintf (stderr, "%s: up arrow\n", blurb()); + if (list_elt < 0) list_elt = s->list_count - 1; else @@ -1217,19 +1119,20 @@ run_prev_cb (GtkButton *button, gpointer user_data) if (list_elt < 0) list_elt = s->list_count - 1; - s->preview_suppressed_p = True; + s->preview_suppressed_p = TRUE; flush_dialog_changes_and_save (s); - force_list_select_item (s, list_widget, list_elt, True); + force_list_select_item (s, list_widget, list_elt, TRUE); populate_demo_window (s, list_elt); - run_hack (s, list_elt, False); + populate_popup_window (s); + run_hack (s, list_elt, FALSE); s->preview_suppressed_p = ops; } -/* Writes the given settings into prefs. - Returns true if there was a change, False otherwise. +/* Writes the settings of the given hack into prefs. + Returns true if there was a change, FALSE otherwise. command and/or visual may be 0, or enabled_p may be -1, meaning "no change". */ static Bool @@ -1240,7 +1143,7 @@ flush_changes (state *s, const char *visual) { saver_preferences *p = &s->prefs; - Bool changed = False; + Bool changed = FALSE; screenhack *hack; int hack_number; if (list_elt < 0 || list_elt >= s->list_count) @@ -1253,7 +1156,7 @@ flush_changes (state *s, enabled_p != hack->enabled_p) { hack->enabled_p = enabled_p; - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": enabled => %d\n", blurb(), hack->name, enabled_p); @@ -1265,7 +1168,7 @@ flush_changes (state *s, { if (hack->command) free (hack->command); hack->command = strdup (command); - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": command => \"%s\"\n", blurb(), hack->name, command); @@ -1281,7 +1184,7 @@ flush_changes (state *s, { if (hack->visual) free (hack->visual); hack->visual = strdup (visual); - changed = True; + changed = TRUE; if (s->debug_p) fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n", blurb(), hack->name, visual); @@ -1302,7 +1205,7 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) { int value; if (!sec_p || strchr (line, ':')) - value = parse_time ((char *) line, sec_p, True); + value = parse_time ((char *) line, sec_p, TRUE); else { char c; @@ -1316,11 +1219,9 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) if (value < 0) { char b[255]; - sprintf (b, - _("Error:\n\n" - "Unparsable time format: \"%s\"\n"), + sprintf (b, _("Unparsable time format: \"%.100s\"\n"), line); - warning_dialog (s->toplevel_widget, b, D_NONE, 100); + warning_dialog (s->window, _("Error"), b); } else *store = value; @@ -1328,97 +1229,6 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p) } -static Bool -directory_p (const char *path) -{ - struct stat st; - if (!path || !*path) - return False; - else if (stat (path, &st)) - return False; - else if (!S_ISDIR (st.st_mode)) - return False; - else - return True; -} - -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 || !*path) return 0; - L = strlen (path); - p2 = (char *) malloc (L + 2); - strcpy (p2, path); - if (p2[L-1] == '/') /* remove trailing slash */ - p2[--L] = 0; - - for (s = p2; s && *s; s++) - { - if (*s == '/' && - (!strncmp (s, "/../", 4) || /* delete "XYZ/../" */ - !strncmp (s, "/..\000", 4))) /* delete "XYZ/..$" */ - { - char *s0 = s; - while (s0 > p2 && s0[-1] != '/') - s0--; - if (s0 > p2) - { - s0--; - s += 3; - /* strcpy (s0, s); */ - memmove(s0, s, strlen(s) + 1); - s = s0-1; - } - } - else if (*s == '/' && !strncmp (s, "/./", 3)) { /* delete "/./" */ - /* strcpy (s, s+2), s--; */ - memmove(s, s+2, strlen(s+2) + 1); - s--; - } - else if (*s == '/' && !strncmp (s, "/.\000", 3)) /* delete "/.$" */ - *s = 0, s--; - } - - /* - Normalize consecutive slashes. - Ignore doubled slashes after ":" to avoid mangling URLs. - */ - - for (s = p2; s && *s; s++){ - if (*s == ':') continue; - if (!s[1] || !s[2]) continue; - while (s[1] == '/' && s[2] == '/') - /* strcpy (s+1, s+2); */ - memmove (s+1, s+2, strlen(s+2) + 1); - } - - /* and strip trailing whitespace for good measure. */ - L = strlen(p2); - while (isspace(p2[L-1])) - p2[--L] = 0; - - return p2; -} - - -#ifdef HAVE_GTK2 - typedef struct { state *s; int i; @@ -1426,10 +1236,10 @@ typedef struct { } FlushForeachClosure; static gboolean -flush_checkbox (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) +flush_checkbox (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { FlushForeachClosure *closure = data; gboolean checked; @@ -1440,7 +1250,7 @@ flush_checkbox (GtkTreeModel *model, if (flush_changes (closure->s, closure->i, checked, 0, 0)) - *closure->changed = True; + *closure->changed = TRUE; closure->i++; @@ -1448,8 +1258,6 @@ flush_checkbox (GtkTreeModel *model, return FALSE; } -#endif /* HAVE_GTK2 */ - static char * theme_name_strip (const char *s) @@ -1476,113 +1284,80 @@ theme_name_strip (const char *s) static Bool flush_dialog_changes_and_save (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; saver_preferences P2, *p2 = &P2; -#ifdef HAVE_GTK2 - GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkTreeView *list_widget = GTK_TREE_VIEW (win->list); GtkTreeModel *model = gtk_tree_view_get_model (list_widget); FlushForeachClosure closure; -#else /* !HAVE_GTK2 */ - GtkList *list_widget = GTK_LIST (name_to_widget (s, "list")); - GList *kids = gtk_container_children (GTK_CONTAINER (list_widget)); - int i; -#endif /* !HAVE_GTK2 */ - static Bool already_warned_about_missing_image_directory = False; /* very long name... */ + Bool changed = FALSE; - Bool changed = False; - GtkWidget *w; - - if (s->saving_p) return False; - s->saving_p = True; + if (s->initializing_p) return FALSE; + if (s->saving_p) return FALSE; + s->saving_p = TRUE; *p2 = *p; - /* Flush any checkbox changes in the list down into the prefs struct. + /* Flush any checkbox changes in the list down into the s2 prefs struct. */ -#ifdef HAVE_GTK2 closure.s = s; closure.changed = &changed; closure.i = 0; gtk_tree_model_foreach (model, flush_checkbox, &closure); -#else /* !HAVE_GTK2 */ - - for (i = 0; kids; kids = kids->next, i++) - { - GtkWidget *line = GTK_WIDGET (kids->data); - GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child); - GtkWidget *line_check = - GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data); - Bool checked = - gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check)); - - if (flush_changes (s, i, (checked ? 1 : 0), 0, 0)) - changed = True; - } -#endif /* ~HAVE_GTK2 */ - /* Flush the non-hack-specific settings down into the prefs struct. */ -# define SECONDS(FIELD,NAME) \ - w = name_to_widget (s, (NAME)); \ - hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True) - -# define MINUTES(FIELD,NAME) \ - w = name_to_widget (s, (NAME)); \ - hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False) - -# define CHECKBOX(FIELD,NAME) \ - w = name_to_widget (s, (NAME)); \ - (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w)) - -# define PATHNAME(FIELD,NAME) \ - w = name_to_widget (s, (NAME)); \ - (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w))) - -# define TEXT(FIELD,NAME) \ - w = name_to_widget (s, (NAME)); \ - (FIELD) = (char *) g_strdup(gtk_entry_get_text (GTK_ENTRY (w))) - - MINUTES (&p2->timeout, "timeout_spinbutton"); - MINUTES (&p2->cycle, "cycle_spinbutton"); - CHECKBOX (p2->lock_p, "lock_button"); - MINUTES (&p2->lock_timeout, "lock_spinbutton"); - - CHECKBOX (p2->dpms_enabled_p, "dpms_button"); - CHECKBOX (p2->dpms_quickoff_p, "dpms_quickoff_button"); - MINUTES (&p2->dpms_standby, "dpms_standby_spinbutton"); - MINUTES (&p2->dpms_suspend, "dpms_suspend_spinbutton"); - MINUTES (&p2->dpms_off, "dpms_off_spinbutton"); - - CHECKBOX (p2->grab_desktop_p, "grab_desk_button"); - CHECKBOX (p2->grab_video_p, "grab_video_button"); - CHECKBOX (p2->random_image_p, "grab_image_button"); - PATHNAME (p2->image_directory, "image_text"); - -#if 0 - CHECKBOX (p2->verbose_p, "verbose_button"); - CHECKBOX (p2->splash_p, "splash_button"); -#endif +# define SECONDS(PREF,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(PREF), TRUE) +# define MINUTES(PREF,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(PREF), FALSE) +# define CHECKBOX(PREF,WIDGET) \ + (PREF) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (win->WIDGET)) +# define PATHNAME(PREF,WIDGET,DIRP) \ + (PREF) = pathname_tilde ( \ + gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), TRUE, (DIRP)) +# define TEXT(PREF,WIDGET) \ + (PREF) = (char *) g_strdup (gtk_entry_get_text (GTK_ENTRY (win->WIDGET))) + + MINUTES (p2->timeout, timeout_spinbutton); + MINUTES (p2->cycle, cycle_spinbutton); + CHECKBOX (p2->lock_p, lock_button); + MINUTES (p2->lock_timeout, lock_spinbutton); + + CHECKBOX (p2->dpms_enabled_p, dpms_button); + CHECKBOX (p2->dpms_quickoff_p, dpms_quickoff_button); + MINUTES (p2->dpms_standby, dpms_standby_spinbutton); + MINUTES (p2->dpms_suspend, dpms_suspend_spinbutton); + MINUTES (p2->dpms_off, dpms_off_spinbutton); + + CHECKBOX (p2->grab_desktop_p, grab_desk_button); + CHECKBOX (p2->grab_video_p, grab_video_button); + CHECKBOX (p2->random_image_p, grab_image_button); + PATHNAME (p2->image_directory, image_text, TRUE); { - 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"); + Bool v = FALSE; + Bool od = s->debug_p; + s->debug_p = 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; + s->debug_p = od; } - CHECKBOX (p2->fade_p, "fade_button"); - CHECKBOX (p2->unfade_p, "unfade_button"); - SECONDS (&p2->fade_seconds, "fade_spinbutton"); + TEXT (p2->text_literal, text_entry); + PATHNAME (p2->text_file, text_file_entry, FALSE); + PATHNAME (p2->text_program, text_program_entry, FALSE); + TEXT (p2->text_url, text_url_entry); + + CHECKBOX (p2->fade_p, fade_button); + CHECKBOX (p2->unfade_p, unfade_button); + SECONDS (p2->fade_seconds, fade_spinbutton); # undef SECONDS # undef MINUTES @@ -1590,35 +1365,9 @@ flush_dialog_changes_and_save (state *s) # undef PATHNAME # undef TEXT - /* Warn if the image directory doesn't exist, when: - - not being warned before - - image directory is changed and the directory doesn't exist - - image directory is not a URL - */ - if (p2->image_directory && - *p2->image_directory && - !directory_p (p2->image_directory) && - strncmp(p2->image_directory, "http://", 7) && - strncmp(p2->image_directory, "https://", 8) && - ( !already_warned_about_missing_image_directory || - ( p->image_directory && - *p->image_directory && - strcmp(p->image_directory, p2->image_directory) - ) - ) - ) - { - char b[255]; - sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n", - p2->image_directory); - if (warning_dialog (s->toplevel_widget, b, D_NONE, 100)) - already_warned_about_missing_image_directory = True; - } - - /* Map the mode menu to `saver_mode' enum values. */ { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); int menu_elt = gtk_combo_box_get_active (opt); if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort(); p2->mode = mode_menu_order[menu_elt]; @@ -1634,10 +1383,10 @@ flush_dialog_changes_and_save (state *s) /* Theme menu. */ { - GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu")); - char *themes = get_string_resource (dpy, "themeNames", "ThemeNames"); + GtkComboBox *cbox = GTK_COMBO_BOX (win->theme_menu); + char *themes = get_string_resource (s->dpy, "themeNames", "ThemeNames"); int menu_index = gtk_combo_box_get_active (cbox); - char *token = themes; + char *token = themes ? themes : "default"; char *name, *last; int i = 0; while ((name = strtok_r (token, ",", &last))) @@ -1646,199 +1395,238 @@ flush_dialog_changes_and_save (state *s) if (i == menu_index) { char *name2 = theme_name_strip (name); - if (p->dialog_theme) free (p->dialog_theme); - p2->dialog_theme = name2; + if (p->dialog_theme && !!strcmp (p->dialog_theme, name2)) + { + free (p->dialog_theme); + p2->dialog_theme = name2; + if (s->debug_p) + fprintf (stderr, "%s: theme => \"%s\"\n", blurb(), + p2->dialog_theme); + } + else + { + free (name2); + } } i++; } } -# define COPY(field, name) \ - if (p->field != p2->field) { \ - changed = True; \ - if (s->debug_p) \ - fprintf (stderr, "%s: %s => %ld\n", blurb(), \ - name, (unsigned long) p2->field); \ - } \ - p->field = p2->field - - COPY(mode, "mode"); - COPY(selected_hack, "selected_hack"); - - COPY(timeout, "timeout"); - COPY(cycle, "cycle"); - COPY(lock_p, "lock_p"); - COPY(lock_timeout, "lock_timeout"); - - COPY(dpms_enabled_p, "dpms_enabled_p"); - COPY(dpms_quickoff_p, "dpms_quickoff_enabled_p"); - COPY(dpms_standby, "dpms_standby"); - COPY(dpms_suspend, "dpms_suspend"); - COPY(dpms_off, "dpms_off"); - -#if 0 - COPY(verbose_p, "verbose_p"); - COPY(splash_p, "splash_p"); -#endif - - COPY(tmode, "tmode"); - - COPY(install_cmap_p, "install_cmap_p"); - COPY(fade_p, "fade_p"); - COPY(unfade_p, "unfade_p"); - COPY(fade_seconds, "fade_seconds"); + /* It is difficult to get "editing completed" events out of GtkEntry. + I want something that fires on RET or focus-out, but I can't seem + to find a consistent way to get that. So let's fake it here. + */ + if (!s->initializing_p && + !!strcmp (p->image_directory, p2->image_directory)) + { + if (s->debug_p) + fprintf (stderr, "%s: imagedir validating \"%s\" -> \"%s\"\n", + blurb(), p->image_directory, p2->image_directory); + if (! validate_image_directory (s, p2->image_directory)) + { + /* Don't save the bad new value into the preferences. */ + if (p2->image_directory != p->image_directory) + free (p2->image_directory); + p2->image_directory = strdup (p->image_directory); + } + } - COPY(grab_desktop_p, "grab_desktop_p"); - COPY(grab_video_p, "grab_video_p"); - COPY(random_image_p, "random_image_p"); - COPY(dialog_theme, "dialog_theme"); + /* Copy any changes from p2 into p, and log them. + */ +# undef STR +# define STR(S) #S +# define COPY(FIELD) \ + if (p->FIELD != p2->FIELD) { \ + changed = TRUE; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s: %ld => %ld\n", blurb(),\ + STR(FIELD), (unsigned long) p->FIELD, \ + (unsigned long) p2->FIELD); \ + } \ + p->FIELD = p2->FIELD + + COPY(mode); + COPY(selected_hack); + + COPY(timeout); + COPY(cycle); + COPY(lock_p); + COPY(lock_timeout); + + COPY(dpms_enabled_p); + COPY(dpms_quickoff_p); + COPY(dpms_standby); + COPY(dpms_suspend); + COPY(dpms_off); + + COPY(tmode); + + COPY(install_cmap_p); + COPY(fade_p); + COPY(unfade_p); + COPY(fade_seconds); + + COPY(grab_desktop_p); + COPY(grab_video_p); + COPY(random_image_p); + + COPY(dialog_theme); # undef COPY -# 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; \ +# define COPYSTR(FIELD) \ + if (!p->FIELD || \ + !p2->FIELD || \ + strcmp(p->FIELD, p2->FIELD)) \ + { \ + changed = TRUE; \ + if (s->debug_p) \ + fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), \ + STR(FIELD), 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"); + COPYSTR(image_directory); + COPYSTR(text_literal); + COPYSTR(text_file); + COPYSTR(text_program); + COPYSTR(text_url); # undef COPYSTR populate_prefs_page (s); if (changed) { - sync_server_dpms_settings (GDK_DISPLAY(), p); - changed = demo_write_init_file (s, p); + if (s->dpy) + sync_server_dpms_settings (s->dpy, p); + demo_write_init_file (s, p); + + /* Tell the xscreensaver daemon to wake up and reload the init file, + in case the timeout has changed. Without this, it would wait + until the *old* timeout had expired before reloading. */ + if (s->debug_p) + fprintf (stderr, "%s: command: DEACTIVATE\n", blurb()); + if (s->dpy) + xscreensaver_command (s->dpy, XA_DEACTIVATE, 0, 0, 0); } - s->saving_p = False; + s->saving_p = FALSE; + return changed; } -/* Flush out any changes made in the popup dialog box (where changes - take place only when the OK button is clicked.) - */ -static Bool -flush_popup_changes_and_save (state *s) +/* Called when any field in the prefs dialog may have been changed. + Referenced by many items in demo.ui. */ +G_MODULE_EXPORT gboolean +pref_changed_cb (GtkWidget *widget, gpointer user_data) { - Bool changed = False; - saver_preferences *p = &s->prefs; - int list_elt = selected_list_element (s); - - GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); - GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo")); - GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))); - - const char *visual = gtk_entry_get_text (visent); - const char *command = gtk_entry_get_text (cmd); - - char c; - unsigned long id; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; - if (s->saving_p) return False; - s->saving_p = True; - - if (list_elt < 0) - goto DONE; - - if (maybe_reload_init_file (s) != 0) + if (s->debug_p) { - changed = True; - goto DONE; + if (s->flushing_p) + fprintf (stderr, "%s: (pref changed: %s)\n", blurb(), + gtk_widget_get_name (widget)); + else + fprintf (stderr, "%s: pref changed: %s\n", blurb(), + gtk_widget_get_name (widget)); } - /* Sanity-check and canonicalize whatever the user typed into the combo box. - */ - if (!strcasecmp (visual, "")) visual = ""; - else if (!strcasecmp (visual, "any")) visual = ""; - else if (!strcasecmp (visual, "default")) visual = "Default"; - else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; - else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; - else if (!strcasecmp (visual, "best")) visual = "Best"; - else if (!strcasecmp (visual, "mono")) visual = "Mono"; - else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; - else if (!strcasecmp (visual, "gray")) visual = "Gray"; - else if (!strcasecmp (visual, "grey")) visual = "Gray"; - else if (!strcasecmp (visual, "color")) visual = "Color"; - else if (!strcasecmp (visual, "gl")) visual = "GL"; - else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; - else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; - else if (!strcasecmp (visual, "truecolor")) visual = "TrueColor"; - else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; - else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; - else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; - else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; - else if (1 == sscanf (visual, " %lu %c", &id, &c)) ; - else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; - else + if (! s->flushing_p) { - gdk_beep (); /* unparsable */ - visual = ""; - gtk_entry_set_text (visent, _("Any")); + s->flushing_p = TRUE; + flush_dialog_changes_and_save (s); + s->flushing_p = FALSE; } + return GDK_EVENT_PROPAGATE; +} - changed = flush_changes (s, list_elt, -1, command, visual); - if (changed) - { - changed = demo_write_init_file (s, p); - - /* Do this to re-launch the hack if (and only if) the command line - has changed. */ - populate_demo_window (s, selected_list_element (s)); - } - DONE: - s->saving_p = False; - return changed; +/* Same as pref_changed_cb but different. */ +G_MODULE_EXPORT gboolean +pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + pref_changed_cb (widget, user_data); + return GDK_EVENT_PROPAGATE; } -G_MODULE_EXPORT void -pref_changed_cb (GtkWidget *widget, gpointer user_data) +/* Called when the timeout or DPMS spinbuttons are changed, by demo.ui. + */ +G_MODULE_EXPORT gboolean +dpms_sanity_cb (GtkWidget *widget, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - if (! s->initializing_p) - { - s->initializing_p = True; - flush_dialog_changes_and_save (s); - s->initializing_p = False; - } + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + Time timeout, standby, suspend, off; + + if (s->flushing_p) return GDK_EVENT_PROPAGATE; + if (s->initializing_p) return GDK_EVENT_PROPAGATE; + if (! s->dpms_supported_p) return GDK_EVENT_PROPAGATE; + + /* Read the current values from the four spinbuttons. */ +# define MINUTES(V,WIDGET) \ + hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (win->WIDGET)), \ + &(V), FALSE) + MINUTES (timeout, timeout_spinbutton); + MINUTES (standby, dpms_standby_spinbutton); + MINUTES (suspend, dpms_suspend_spinbutton); + MINUTES (off, dpms_off_spinbutton); +# undef MINUTES + + /* If the DPMS settings are non-zero, they must not go backwards: + standby >= timeout (screen saver activation) + suspend >= standby + off >= suspend + */ +# define MINUTES(V,LOWER,WIDGET) \ + if ((V) != 0 && (V) < LOWER) \ + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->WIDGET), \ + (double) ((LOWER) + 59) / (60 * 1000)) + MINUTES (standby, timeout, dpms_standby_spinbutton); + MINUTES (suspend, standby, dpms_suspend_spinbutton); + MINUTES (off, standby, dpms_off_spinbutton); + MINUTES (off, suspend, dpms_off_spinbutton); +# undef MINUTES + + return GDK_EVENT_PROPAGATE; } + +/* Same as dpms_sanity_cb but different. */ G_MODULE_EXPORT gboolean -pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +dpms_sanity_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { - pref_changed_cb (widget, user_data); - return FALSE; + dpms_sanity_cb (widget, user_data); + return GDK_EVENT_PROPAGATE; } + + /* Callback on menu items in the "mode" options menu. */ G_MODULE_EXPORT void mode_menu_item_cb (GtkWidget *widget, gpointer user_data) { - state *s = (state *) user_data; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; saver_preferences *p = &s->prefs; - GtkWidget *list = name_to_widget (s, "list"); + GtkWidget *list = win->list; int list_elt; int menu_index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); saver_mode new_mode = mode_menu_order[menu_index]; + if (s->flushing_p) return; /* Called as a spurious side-effect */ + if (s->initializing_p) return; + + if (s->debug_p) fprintf (stderr, "%s: mode menu\n", blurb()); + /* Keep the same list element displayed as before; except if we're switching *to* "one screensaver" mode from any other mode, set "the one" to be that which is currently selected. @@ -1851,7 +1639,8 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) saver_mode old_mode = p->mode; p->mode = new_mode; populate_demo_window (s, list_elt); - force_list_select_item (s, list, list_elt, True); + populate_popup_window (s); + force_list_select_item (s, list, list_elt, TRUE); p->mode = old_mode; /* put it back, so the init file gets written */ } @@ -1859,52 +1648,94 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data) } +/* Remove the "random-same" item from the screen saver mode menu + (we don't display that unless there are multiple screens.) + */ +static void +hide_mode_menu_random_same (state *s) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); + GtkTreeModel *list = gtk_combo_box_get_model (opt); + unsigned int i; + for (i = 0; i < countof(mode_menu_order); i++) + { + if (mode_menu_order[i] == RANDOM_HACKS_SAME) + { + GtkTreeIter iter; + gtk_tree_model_iter_nth_child (list, &iter, NULL, i); + gtk_list_store_remove (GTK_LIST_STORE (list), &iter); + break; + } + } + + /* recompute option-menu size */ + gtk_widget_unrealize (GTK_WIDGET (opt)); + gtk_widget_realize (GTK_WIDGET (opt)); +} + + +/* Called when a new tab is selected. */ G_MODULE_EXPORT void -switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, +switch_page_cb (GtkNotebook *notebook, GtkWidget *page, gint page_num, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + + if (s->debug_p) fprintf (stderr, "%s: tab changed\n", blurb()); + populate_prefs_page (s); pref_changed_cb (GTK_WIDGET (notebook), user_data); /* If we're switching to page 0, schedule the current hack to be run. Otherwise, schedule it to stop. */ if (page_num == 0) - populate_demo_window (s, selected_list_element (s)); + { + populate_demo_window (s, selected_list_element (s)); + populate_popup_window (s); + } else schedule_preview (s, 0); } -#ifdef HAVE_GTK2 + +/* Called when a line is double-clicked in the saver list. */ static void -list_activated_cb (GtkTreeView *list, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer data) +list_activated_cb (GtkTreeView *list, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) { state *s = data; char *str; int list_elt; - if (gdk_pointer_is_grabbed()) return; + if (s->debug_p) fprintf (stderr, "%s: list activated\n", blurb()); + + /* I did this in Gtk 2 and I don't remember why: + if (gdk_pointer_is_grabbed()) return; + I don't understand how to use gdk_display_device_is_grabbed(). + */ str = gtk_tree_path_to_string (path); list_elt = strtol (str, NULL, 10); g_free (str); if (list_elt >= 0) - run_hack (s, list_elt, True); + run_hack (s, list_elt, TRUE); } +/* Called when a line is selected/highlighted in the saver list. */ static void list_select_changed_cb (GtkTreeSelection *selection, gpointer data) { - state *s = (state *)data; + state *s = (state *) data; GtkTreeModel *model; GtkTreeIter iter; GtkTreePath *path; char *str; int list_elt; + if (s->debug_p) fprintf (stderr, "%s: list selection changed\n", blurb()); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return; @@ -1916,65 +1747,14 @@ list_select_changed_cb (GtkTreeSelection *selection, gpointer data) g_free (str); 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. - */ + in the list, in case both windows are currently visible. */ populate_popup_window (s); -} - -#else /* !HAVE_GTK2 */ - -static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the - list_select_cb that comes in - *after* we've double-clicked. - */ - -static gint -list_doubleclick_cb (GtkWidget *button, GdkEventButton *event, - gpointer data) -{ - state *s = (state *) data; - if (event->type == GDK_2BUTTON_PRESS) - { - GtkList *list = GTK_LIST (name_to_widget (s, "list")); - int list_elt = gtk_list_child_position (list, GTK_WIDGET (button)); - - last_doubleclick_time = time ((time_t *) 0); - - if (list_elt >= 0) - run_hack (s, list_elt, True); - } - - return FALSE; -} - - -static void -list_select_cb (GtkList *list, GtkWidget *child, gpointer data) -{ - state *s = (state *) data; - time_t now = time ((time_t *) 0); - if (now >= last_doubleclick_time + 2) - { - int list_elt = gtk_list_child_position (list, GTK_WIDGET (child)); - populate_demo_window (s, list_elt); - flush_dialog_changes_and_save (s); - } -} - -static void -list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data) -{ - state *s = (state *) data; - populate_demo_window (s, -1); flush_dialog_changes_and_save (s); } -#endif /* !HAVE_GTK2 */ - /* Called when the checkboxes that are in the left column of the scrolling list are clicked. This both populates the right pane @@ -1982,39 +1762,25 @@ list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data) also syncs this checkbox with the right pane Enabled checkbox. */ static void -list_checkbox_cb ( -#ifdef HAVE_GTK2 - GtkCellRendererToggle *toggle, - gchar *path_string, -#else /* !HAVE_GTK2 */ - GtkWidget *cb, -#endif /* !HAVE_GTK2 */ - gpointer data) +list_checkbox_cb (GtkCellRendererToggle *toggle, + gchar *path_string, gpointer data) { state *s = (state *) data; -#ifdef HAVE_GTK2 - GtkScrolledWindow *scroller = - GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller")); - GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (win->scroller); + GtkTreeView *list = GTK_TREE_VIEW (win->list); GtkTreeModel *model = gtk_tree_view_get_model (list); GtkTreePath *path = gtk_tree_path_new_from_string (path_string); GtkTreeIter iter; gboolean active; -#else /* !HAVE_GTK2 */ - GtkWidget *line_hbox = GTK_WIDGET (cb)->parent; - GtkWidget *line = GTK_WIDGET (line_hbox)->parent; - - GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent); - GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent); - GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent); -#endif /* !HAVE_GTK2 */ GtkAdjustment *adj; double scroll_top; int list_elt; -#ifdef HAVE_GTK2 + if (s->debug_p) fprintf (stderr, "%s: list checkbox\n", blurb()); + if (!gtk_tree_model_get_iter (model, &iter, path)) { g_warning ("bad path: %s", path_string); @@ -2031,17 +1797,15 @@ list_checkbox_cb ( -1); list_elt = strtol (path_string, NULL, 10); -#else /* !HAVE_GTK2 */ - list_elt = gtk_list_child_position (list, line); -#endif /* !HAVE_GTK2 */ /* remember previous scroll position of the top of the list */ adj = gtk_scrolled_window_get_vadjustment (scroller); - scroll_top = GET_ADJ_VALUE (adj); + scroll_top = gtk_adjustment_get_value (adj); flush_dialog_changes_and_save (s); - force_list_select_item (s, GTK_WIDGET (list), list_elt, False); + force_list_select_item (s, GTK_WIDGET (list), list_elt, FALSE); populate_demo_window (s, list_elt); + populate_popup_window (s); /* restore the previous scroll position of the top of the list. this is weak, but I don't really know why it's moving... */ @@ -2049,373 +1813,538 @@ list_checkbox_cb ( } -typedef struct { - state *state; - GtkFileSelection *widget; -} file_selection_data; - - - +/* If the directory or URL does not have images in it, pop up a warning + dialog. This happens at startup, and is fast. + */ static void -store_image_directory (GtkWidget *button, gpointer user_data) +validate_image_directory_quick (state *s) { - 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); + char *warn = 0; + char buf[10240]; + + if (!p->random_image_p) return; + + if (!p->image_directory || !*p->image_directory) + warn = _("Image directory is unset"); + else if (!strncmp (p->image_directory, "http://", 7) || + !strncmp (p->image_directory, "https://", 8)) + warn = 0; + else if (!directory_p (p->image_directory)) + warn = _("Image directory does not exist"); + else if (!image_files_p (p->image_directory, 10)) + warn = _("Image directory is empty"); + + if (!warn) return; + + sprintf (buf, + _("%.100s:\n\n" + " %.100s\n\n" + "Select the 'Advanced' tab and choose a directory with some\n" + "pictures in it, or you're going to see a lot of boring colorbars!"), + warn, + (p->image_directory ? p->image_directory : "")); + warning_dialog (s->window, _("Warning"), buf); +} - if (p->image_directory && !strcmp(p->image_directory, path)) - return; /* no change */ - /* No warning for URLs. */ - if ((!directory_p (path)) && strncmp(path, "http://", 6)) - { - char b[255]; - sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path); - warning_dialog (GTK_WIDGET (top), b, D_NONE, 100); - return; - } +/* "Cancel" button on the validate image directory progress dialog. */ +static void +validate_image_directory_cancel_cb (GtkDialog *dialog, gint response_id, + gpointer user_data) +{ + Bool *closed = (Bool *) user_data; + *closed = TRUE; + gtk_widget_destroy (GTK_WIDGET (dialog)); +} - if (p->image_directory) free (p->image_directory); - p->image_directory = normalize_directory (path); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")), - (p->image_directory ? p->image_directory : "")); - demo_write_init_file (s, p); +typedef struct { + GtkWidget *dialog; + int timer_id; +} validate_timer_closure; + +static int +validate_timer_show (gpointer data) +{ + validate_timer_closure *vtc = (validate_timer_closure *) data; + gtk_widget_show_all (vtc->dialog); + vtc->timer_id = 0; + return FALSE; } -static void -store_text_file (GtkWidget *button, gpointer user_data) +/* If the directory or URL does not have images in it, pop up a warning + dialog and return false. This happens when the imageDirectory preference + is edited, and might be slow. + + It does this by running "xscreensaver-getimage-file", which has the side + effect of populating the image cache for that directory. Since that will + take a while if there are a lot of files, this also pops up a progress + dialog with a spinner in it, and a cancel button. That progress dialog + only pops up if the validation has already been running for a little + while, so that it doesn't flicker for small or pre-cached directories. + */ +static Bool +validate_image_directory (state *s, const char *path) { - 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); + validate_timer_closure vtc; + char buf[1024]; + char err[1024]; + GtkWidget *dialog, *content_area, *label, *spinner; + int margin = 32; + Bool closed_p = FALSE; + + dialog = gtk_dialog_new_with_buttons (_("XScreenSaver Image Cache"), + s->window, + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Cancel"), GTK_RESPONSE_CLOSE, + NULL); + + sprintf (buf, _("Populating image cache for \"%.100s\"..."), path); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + label = gtk_label_new (buf); + + gtk_widget_set_margin_start (label, margin); + gtk_widget_set_margin_end (label, margin); + gtk_widget_set_margin_top (label, margin); + gtk_widget_set_margin_bottom (label, margin / 2); + gtk_container_add (GTK_CONTAINER (content_area), label); + + spinner = gtk_spinner_new(); + gtk_spinner_start (GTK_SPINNER (spinner)); + gtk_container_add (GTK_CONTAINER (content_area), spinner); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (validate_image_directory_cancel_cb), + &closed_p); + g_signal_connect (dialog, "close", + G_CALLBACK (validate_image_directory_cancel_cb), + &closed_p); + + /* Only pop up the dialog box with the spinner if this has already taken + a little while, so that if it completes immediately, we don't flicker. + */ + vtc.dialog = dialog; + vtc.timer_id = g_timeout_add (1000, validate_timer_show, &vtc); - if (p->text_file && !strcmp(p->text_file, path)) - return; /* no change */ + while (gtk_events_pending ()) /* Paint the window now. */ + gtk_main_iteration (); - 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, D_NONE, 100); - return; - } + { + pid_t forked; + int fds [2]; + int in, out; - if (p->text_file) free (p->text_file); - p->text_file = normalize_directory (path); + char *av[10]; + int ac = 0; - 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); -} + *err = 0; + av[ac++] = "xscreensaver-getimage-file"; + av[ac++] = (char *) path; + av[ac] = 0; + if (pipe (fds)) + { + strcpy (err, "error creating pipe"); + goto FAIL; + } -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); + in = fds [0]; + out = fds [1]; - if (p->text_program && !strcmp(p->text_program, path)) - return; /* no change */ + switch ((int) (forked = fork ())) + { + case -1: + { + strcpy (err, "couldn't fork"); + goto FAIL; + } + case 0: /* Child fork */ + { + int stderr_fd = 2; -# 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, D_NONE, 100); - return; - } -# endif + close (in); /* don't need this one */ + if (! s->debug_p) + close (fileno (stdout)); + close (ConnectionNumber (s->dpy)); /* close display fd */ - if (p->text_program) free (p->text_program); - p->text_program = normalize_directory (path); + if (dup2 (out, stderr_fd) < 0) /* pipe stdout */ + { + perror ("could not dup() a new stderr:"); + exit (1); + } - 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); -} + execvp (av[0], av); /* shouldn't return. */ + 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: /* Parent fork */ + { + char *ss = err; + int bufsiz = sizeof(err); -static void -browse_image_dir_cancel (GtkWidget *button, gpointer user_data) -{ - file_selection_data *fsd = (file_selection_data *) user_data; - gtk_widget_hide (GTK_WIDGET (fsd->widget)); -} + close (out); /* don't need this one */ -static void -browse_image_dir_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_image_directory (button, user_data); -} + if (s->debug_p) + fprintf (stderr, "%s: forked %s\n", blurb(), av[0]); -static void -browse_text_file_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_text_file (button, user_data); -} + while (1) + { + fd_set rset; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000000 / 10; /* Repaint widgets at 10 fps */ + FD_ZERO (&rset); + FD_SET (in, &rset); + if (0 < select (in+1, &rset, 0, 0, &tv)) + { + int n = read (in, (void *) ss, bufsiz); + if (n <= 0) + { + if (s->debug_p) + fprintf (stderr, "%s: %s: read EOF\n", blurb(), av[0]); + break; + } + else + { + ss += n; + bufsiz -= n; + *ss = 0; + + if (s->debug_p) + fprintf (stderr, "%s: %s: read: \"%s\"\n", blurb(), + av[0], ss - n); + } + } -static void -browse_text_program_ok (GtkWidget *button, gpointer user_data) -{ - browse_image_dir_cancel (button, user_data); - store_text_program (button, user_data); -} + /* Service Gtk events and timers */ + while (gtk_events_pending ()) + gtk_main_iteration (); -static void -browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data) -{ - browse_image_dir_cancel (widget, user_data); -} + if (closed_p) + { + kill (forked, SIGTERM); + if (s->debug_p) + fprintf (stderr, "%s: cancel\n", blurb()); + break; + } + } -G_MODULE_EXPORT void -browse_image_dir_cb (GtkButton *button, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - saver_preferences *p = &s->prefs; - static file_selection_data *fsd = 0; + *ss = 0; + close (in); - GtkFileSelection *selector = GTK_FILE_SELECTION( - gtk_file_selection_new ("Please select the image directory.")); + if (s->debug_p) + fprintf (stderr, "%s: %s exited\n", blurb(), av[0]); - if (!fsd) - fsd = (file_selection_data *) malloc (sizeof (*fsd)); + /* Wait for the child to die. */ + { + int wait_status = 0; + waitpid (-1, &wait_status, 0); + } + } + } + } - fsd->widget = selector; - fsd->state = s; + if (vtc.timer_id) /* Remove the popup timer if it hasn't fired. */ + g_source_remove (vtc.timer_id); - if (p->image_directory && *p->image_directory) - gtk_file_selection_set_filename (selector, p->image_directory); + if (s->debug_p) + fprintf (stderr, "%s: dismiss\n", blurb()); - gtk_signal_connect (GTK_OBJECT (selector->ok_button), - "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok), - (gpointer *) fsd); - gtk_signal_connect (GTK_OBJECT (selector->cancel_button), - "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel), - (gpointer *) fsd); - gtk_signal_connect (GTK_OBJECT (selector), "delete_event", - GTK_SIGNAL_FUNC (browse_image_dir_close), - (gpointer *) fsd); + if (! closed_p) + gtk_widget_destroy (dialog); - gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False); + FAIL: + if (*err) + { + warning_dialog (s->window, _("Warning"), err); + return FALSE; + } - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + return TRUE; } -G_MODULE_EXPORT void -browse_text_file_cb (GtkButton *button, gpointer user_data) +/* Called when the imageDirectory text field is edited directly (focus-out). + */ +G_MODULE_EXPORT gboolean +image_text_pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ +#if 0 /* This is handled in flush_dialog_changes_and_save now */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; 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; + GtkEntry *w = GTK_ENTRY (win->image_text); + const char *str = gtk_entry_get_text (w); + char *path = pathname_tilde (str, TRUE, TRUE); - if (p->text_file && *p->text_file) - gtk_file_selection_set_filename (selector, p->text_file); + if (s->debug_p) fprintf (stderr, "%s: imagedir text edited\n", blurb()); - 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); + if (p->image_directory && strcmp(p->image_directory, path)) + { + if (s->debug_p) + fprintf (stderr, "%s: imagedir validating \"%s\" -> \"%s\"\n", blurb(), + p->image_directory, path); + if (! validate_image_directory (s, path)) + { + /* Don't save the bad new value into the preferences. */ + free (path); + return GDK_EVENT_PROPAGATE; + } + } - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + free (path); +# endif + pref_changed_event_cb (widget, event, user_data); + return GDK_EVENT_PROPAGATE; } -G_MODULE_EXPORT void -browse_text_program_cb (GtkButton *button, gpointer user_data) +/* Run a modal file selector dialog. + Select a file, directory, or program. + Normalize the resultant path and store it into the string pointer. + Also update the text field with the new path. + Returns true if any changes made. + */ +gboolean +file_chooser (GtkWindow *parent, GtkEntry *entry, char **retP, + const char *title, gboolean verbose_p, + gboolean dir_p, gboolean program_p) { - 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.")); + gint res; + gboolean changed_p = FALSE; + GtkWidget *dialog = + gtk_file_chooser_dialog_new (title, parent, + (dir_p + ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + : GTK_FILE_CHOOSER_ACTION_OPEN), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Select"), GTK_RESPONSE_ACCEPT, + NULL); + const char *old = gtk_entry_get_text (entry); /* not *retP */ + + if (*old) + { + char *p2 = pathname_tilde (old, FALSE, dir_p); + GFile *gf; - if (!fsd) - fsd = (file_selection_data *) malloc (sizeof (*fsd)); + /* If it's a command line and it begins with an absolute path, + default to that file and its directory. */ + if (program_p && (*p2 == '/' || *p2 == '~')) + { + char *s = strpbrk (p2, " \t\r\n"); + if (s) *s = 0; + program_p = FALSE; + } - fsd->widget = selector; - fsd->state = s; + gf = g_file_new_for_path (p2); + if (! program_p) + { + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), gf, NULL); + if (verbose_p) + fprintf (stderr, "%s: chooser: default \"%s\"\n", blurb(), p2); + } + free (p2); + g_object_unref (gf); + } - if (p->text_program && *p->text_program) - gtk_file_selection_set_filename (selector, p->text_program); + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_ACCEPT) + { + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + char *str = gtk_file_chooser_get_filename (chooser); + char *path = pathname_tilde (str, TRUE, dir_p); + g_free (str); - 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); + if (*retP && !strcmp (*retP, path)) + { + if (verbose_p) + fprintf (stderr, "%s: chooser: unchanged\n", blurb()); + free (path); /* no change */ + } + else if (dir_p && !directory_p (path)) + { + char b[255]; + sprintf (b, _("Directory does not exist: \"%.100s\"\n"), path); + warning_dialog (parent, _("Error"), b); + free (path); /* no change */ + } + else if (!dir_p && !file_p (path)) + { + char b[255]; + sprintf (b, _("File does not exist: \"%.100s\"\n"), path); + warning_dialog (parent, _("Error"), b); + free (path); /* no change */ + } + else + { + if (verbose_p) + fprintf (stderr, "%s: chooser: \"%s\" -> \"%s\n", + blurb(), *retP, path); + if (*retP) free (*retP); + *retP = path; + gtk_entry_set_text (entry, path); + changed_p = TRUE; + } + } + else if (verbose_p) + fprintf (stderr, "%s: chooser: cancelled\n", blurb()); - gtk_window_set_modal (GTK_WINDOW (selector), True); - gtk_widget_show (GTK_WIDGET (selector)); + gtk_widget_destroy (dialog); + return changed_p; } +/* The "Browse" button next to the imageDirectory text field. */ G_MODULE_EXPORT void -preview_theme_cb (GtkWidget *w, gpointer user_data) +browse_image_dir_cb (GtkButton *button, gpointer user_data) { - if (system ("xscreensaver-auth --splash &") < 0) - fprintf (stderr, "%s: splash exec failed\n", blurb()); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; + char *old = strdup (p->image_directory); + + if (s->debug_p) fprintf (stderr, "%s: imagedir browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->image_text), + &p->image_directory, + _("Please select the image directory."), + s->debug_p, TRUE, FALSE)) + { + if (validate_image_directory (s, p->image_directory)) + demo_write_init_file (s, p); + else + { + /* Don't save the bad new value into the preferences. */ + free (p->image_directory); + p->image_directory = old; + old = 0; + } + } + + if (old) free (old); } +/* The "Browse" button next to the textFile text field. */ G_MODULE_EXPORT void -settings_cb (GtkButton *button, gpointer user_data) +browse_text_file_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - int list_elt = selected_list_element (s); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; - populate_demo_window (s, list_elt); /* reset the widget */ - populate_popup_window (s); /* create UI on popup window */ - gtk_widget_show (s->popup_widget); + if (s->debug_p) fprintf (stderr, "%s: textfile browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->text_file_entry), + &p->text_file, + _("Please select a text file."), + s->debug_p, FALSE, FALSE)) + demo_write_init_file (s, p); } -static void -settings_sync_cmd_text (state *s) -{ -# ifdef HAVE_XML - GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); - char *cmd_line = get_configurator_command_line (s->cdata, False); - gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); - gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line)); - free (cmd_line); -# endif /* HAVE_XML */ -} +/* The "Browse" button next to the textProgram text field. */ G_MODULE_EXPORT void -settings_adv_cb (GtkButton *button, gpointer user_data) +browse_text_program_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; - settings_sync_cmd_text (s); - gtk_notebook_set_page (notebook, 1); + if (s->debug_p) fprintf (stderr, "%s: textprogram browse button\n", blurb()); + if (file_chooser (GTK_WINDOW (win), + GTK_ENTRY (win->text_program_entry), + &p->text_program, + _("Please select a text-generating program."), + s->debug_p, FALSE, TRUE)) + demo_write_init_file (s, p); } -G_MODULE_EXPORT void -settings_std_cb (GtkButton *button, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); - - /* Re-create UI to reflect the in-progress command-line settings. */ - populate_popup_window (s); - - gtk_notebook_set_page (notebook, 0); -} +/* The "Preview" button next to the Theme option menu. */ G_MODULE_EXPORT void -settings_reset_cb (GtkButton *button, gpointer user_data) +preview_theme_cb (GtkWidget *w, gpointer user_data) { -# ifdef HAVE_XML - state *s = global_state_kludge; /* I hate C so much... */ - GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text")); - char *cmd_line = get_configurator_command_line (s->cdata, True); - gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); - gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line)); - free (cmd_line); - populate_popup_window (s); -# endif /* HAVE_XML */ -} + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + int ac = 0; + char *av[10]; -G_MODULE_EXPORT void -settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page, - gint page_num, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - GtkWidget *adv = name_to_widget (s, "adv_button"); - GtkWidget *std = name_to_widget (s, "std_button"); + if (s->debug_p) fprintf (stderr, "%s: preview theme button\n", blurb()); - if (page_num == 0) - { - gtk_widget_show (adv); - gtk_widget_hide (std); - } - else if (page_num == 1) - { - gtk_widget_hide (adv); - gtk_widget_show (std); - } - else - abort(); + /* Settings button is disabled with --splash --splash so that we don't + end up with two copies of xscreensaver-settings running. */ + av[ac++] = "xscreensaver-auth"; + av[ac++] = "--splash"; + av[ac++] = "--splash"; + av[ac] = 0; + fork_and_exec (s, ac, av); } - +/* The "Settings" button on the main page. */ G_MODULE_EXPORT void -settings_cancel_cb (GtkButton *button, gpointer user_data) +settings_cb (GtkButton *button, gpointer user_data) { - state *s = global_state_kludge; /* I hate C so much... */ - gtk_widget_hide (s->popup_widget); -} + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (user_data); + state *s = &win->state; + saver_preferences *p = &s->prefs; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + int list_elt = selected_list_element (s); -G_MODULE_EXPORT void -settings_ok_cb (GtkButton *button, gpointer user_data) -{ - state *s = global_state_kludge; /* I hate C so much... */ - GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); - int page = gtk_notebook_get_current_page (notebook); + if (s->debug_p) fprintf (stderr, "%s: settings button\n", blurb()); - if (page == 0) - /* Regenerate the command-line from the widget contents before saving. - But don't do this if we're looking at the command-line page already, - or we will blow away what they typed... */ - settings_sync_cmd_text (s); + populate_demo_window (s, list_elt); /* reset the widget */ + populate_popup_window (s); /* create UI on popup window */ - flush_popup_changes_and_save (s); - gtk_widget_hide (s->popup_widget); -} + /* Pre-select the "Standard" page. */ + settings_std_cb (NULL, s->dialog); + settings_switch_page_cb (GTK_NOTEBOOK (dialog->opt_notebook), NULL, 0, + s->dialog); -static gboolean -wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) -{ - state *s = (state *) data; - settings_cancel_cb (0, (gpointer) s); - return TRUE; + /* If there is no saved position for the dialog, position it to the + right of the main window. See also restore_window_position(), + which already ran at startup. */ + { + int win_x, win_y, dialog_x, dialog_y; + char dummy; + char *old = p->settings_geom; + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_x <= 0 && dialog_y <= 0) + { + int win_w, win_h; + gtk_window_get_position (GTK_WINDOW (s->window), &win_x, &win_y); + gtk_window_get_size (GTK_WINDOW (s->window), &win_w, &win_h); + dialog_x = win_x + win_w + 8; + dialog_y = win_y; + gtk_window_move (GTK_WINDOW (s->dialog), dialog_x, dialog_y); + } + } + + gtk_widget_show (GTK_WIDGET (s->dialog)); } - /* Populating the various widgets */ @@ -2423,18 +2352,18 @@ wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data) /* Returns the number of the last hack run by the server. */ static int -server_current_hack (void) +server_current_hack (state *s) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *dataP = 0; - Display *dpy = GDK_DISPLAY(); int hack_number = -1; - if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + if (!s->dpy) return hack_number; + if (XGetWindowProperty (s->dpy, RootWindow(s->dpy, 0), /* always screen #0 */ XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, + 0, 3, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -2458,13 +2387,14 @@ server_current_hack (void) static void scroll_to_current_hack (state *s) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); saver_preferences *p = &s->prefs; int hack_number = -1; if (p->mode == ONE_HACK) /* in "one" mode, use the one */ hack_number = p->selected_hack; if (hack_number < 0) /* otherwise, use the last-run */ - hack_number = server_current_hack (); + hack_number = server_current_hack (s); if (hack_number < 0) /* failing that, last "one mode" */ hack_number = p->selected_hack; if (hack_number < 0) /* failing that, newest hack. */ @@ -2489,9 +2419,10 @@ scroll_to_current_hack (state *s) 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); + GtkWidget *list = win->list; + force_list_select_item (s, list, list_elt, TRUE); populate_demo_window (s, list_elt); + populate_popup_window (s); } } @@ -2499,10 +2430,8 @@ 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")); + GtkTreeView *list = GTK_TREE_VIEW (XSCREENSAVER_WINDOW (s->window)->list); GtkListStore *model; GtkTreeSelection *selection; GtkCellRenderer *ren; @@ -2560,7 +2489,7 @@ populate_hack_list (state *s) pretty_name = (hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)); + : make_hack_name (s->dpy, hack->command)); if (!available_p) { @@ -2568,16 +2497,15 @@ populate_hack_list (state *s) (but don't actually make it be insensitive, since we still want to be able to click on it.) */ - GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list)); - GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE]; - /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */ + GtkStyleContext *c = + gtk_widget_get_style_context (GTK_WIDGET (list)); + GdkRGBA fg; 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, */ + gtk_style_context_get_color (c, GTK_STATE_FLAG_INSENSITIVE, &fg); + sprintf (buf, "<span foreground=\"#%02X%02X%02X\">%s</span>", + (unsigned int) (0xFF * fg.red), + (unsigned int) (0xFF * fg.green), + (unsigned int) (0xFF * fg.blue), pretty_name); free (pretty_name); pretty_name = buf; @@ -2590,107 +2518,13 @@ populate_hack_list (state *s) -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) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); saver_preferences *p = &s->prefs; Bool sensitive = (p->mode == RANDOM_HACKS || p->mode == RANDOM_HACKS_SAME || @@ -2699,73 +2533,32 @@ update_list_sensitivity (state *s) p->mode == RANDOM_HACKS_SAME); 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")); + GtkTreeView *list = GTK_TREE_VIEW (win->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_widget_set_sensitive (GTK_WIDGET (win->scroller), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (win->next_prev_hbox), sensitive); + gtk_widget_set_sensitive (GTK_WIDGET (win->blanking_table), blankable); 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) { - Display *dpy = GDK_DISPLAY(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); saver_preferences *p = &s->prefs; - Bool can_lock_p = True; + 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; +# ifdef NO_LOCKING + 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) + if (!s->multi_screen_p && p->mode == RANDOM_HACKS_SAME) p->mode = RANDOM_HACKS; @@ -2780,108 +2573,100 @@ populate_prefs_page (state *s) # 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)) - + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->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); - + gtk_spin_button_set_value (GTK_SPIN_BUTTON (win->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); -#if 0 - TOGGLE_ACTIVE ("verbose_button", p->verbose_p); - TOGGLE_ACTIVE ("splash_button", p->splash_p); -#endif - TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p); - TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_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 ("fade_button", p->fade_p); - TOGGLE_ACTIVE ("unfade_button", p->unfade_p); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (win->NAME), (ACTIVEP)) + + TOGGLE_ACTIVE (lock_button, p->lock_p); + TOGGLE_ACTIVE (dpms_button, p->dpms_enabled_p && s->dpms_supported_p); + TOGGLE_ACTIVE (dpms_quickoff_button, (p->dpms_quickoff_p && + s->dpms_supported_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 (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; + 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")), + gtk_entry_set_text (GTK_ENTRY (win->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"), + gtk_widget_set_sensitive (win->image_text, p->random_image_p); + gtk_widget_set_sensitive (win->image_browse_button, p->random_image_p); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_entry), (p->text_literal ? p->text_literal : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_file_entry), (p->text_file ? p->text_file : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_program_entry), (p->text_program ? p->text_program : "")); - gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")), + gtk_entry_set_text (GTK_ENTRY (win->text_url_entry), (p->text_url ? p->text_url : "")); - gtk_widget_set_sensitive (name_to_widget (s, "text_entry"), + gtk_widget_set_sensitive (win->text_entry, p->tmode == TEXT_LITERAL); - gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"), + gtk_widget_set_sensitive (win->text_file_entry, p->tmode == TEXT_FILE); - gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"), + gtk_widget_set_sensitive (win->text_file_browse, p->tmode == TEXT_FILE); - gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"), + gtk_widget_set_sensitive (win->text_program_entry, p->tmode == TEXT_PROGRAM); - gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"), + gtk_widget_set_sensitive (win->text_program_browse, p->tmode == TEXT_PROGRAM); - gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"), + gtk_widget_set_sensitive (win->text_url_entry, p->tmode == TEXT_URL); - /* Theme menu */ { - GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu")); - - /* Without this, pref_changed_cb gets called an exponentially-increasing - number of times on the themes menu, despite the call to - gtk_list_store_clear(). */ - static Bool done_once = False; + GtkComboBox *cbox = GTK_COMBO_BOX (win->theme_menu); - if (cbox && !done_once) + if (cbox) { - char *themes = get_string_resource (dpy, "themeNames", "ThemeNames"); - char *token = themes; - char *name, *name2, *last; + char *themes = get_string_resource(s->dpy, "themeNames", "ThemeNames"); + char *token = themes ? themes : strdup ("default"); + char *name, *last = 0; GtkListStore *model; GtkTreeIter iter; int i = 0; - done_once = True; - - g_object_get (G_OBJECT (cbox), "model", &model, NULL); - if (!model) abort(); - gtk_list_store_clear (model); + /* Bad things happen if we do these things more than once. */ + static Bool model_built_p = FALSE; + static Bool signal_connected_p = FALSE; - gtk_signal_connect (GTK_OBJECT (cbox), "changed", - GTK_SIGNAL_FUNC (pref_changed_cb), (gpointer) s); + if (! model_built_p) + { + g_object_get (G_OBJECT (cbox), "model", &model, NULL); + if (!model) abort(); + gtk_list_store_clear (model); + } while ((name = strtok_r (token, ",", &last))) { + char *name2; int L; token = 0; @@ -2893,22 +2678,33 @@ populate_prefs_page (state *s) name[L-1] == '\n')) name[--L] = 0; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, 0, name, -1); + if (! model_built_p) + { + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, 0, name, -1); + } name2 = theme_name_strip (name); - if (!strcmp (p->dialog_theme, name2)) + if (p->dialog_theme && name2 && !strcmp (p->dialog_theme, name2)) gtk_combo_box_set_active (cbox, i); free (name2); i++; } + + model_built_p = TRUE; + + if (! signal_connected_p) + { + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (pref_changed_cb), (gpointer) win); + signal_connected_p = TRUE; + } } } - /* Map the `saver_mode' enum to mode menu to values. */ { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); + GtkComboBox *opt = GTK_COMBO_BOX (win->mode_menu); int i; for (i = 0; i < countof(mode_menu_order); i++) @@ -2918,146 +2714,183 @@ populate_prefs_page (state *s) update_list_sensitivity (s); } - { - Bool dpms_supported = False; - Display *dpy = GDK_DISPLAY(); - -#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 (win->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); -# 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); - SENSITIZE ("dpms_quickoff_button", dpms_supported); - - SENSITIZE ("fade_label", (p->fade_p || p->unfade_p)); - SENSITIZE ("fade_spinbutton", (p->fade_p || p->unfade_p)); + /* DPMS + */ + SENSITIZE (dpms_button, s->dpms_supported_p); + SENSITIZE (dpms_standby_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_standby_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_standby_spinbutton,s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_suspend_spinbutton,s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_label, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_mlabel, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_off_spinbutton, s->dpms_supported_p && p->dpms_enabled_p); + SENSITIZE (dpms_quickoff_button, s->dpms_supported_p); + + SENSITIZE (fade_label, (p->fade_p || p->unfade_p)); + SENSITIZE (fade_spinbutton, (p->fade_p || p->unfade_p)); # undef SENSITIZE - } -} - -/* Allow the documentation label to re-flow when the text is changed. - http://blog.borovsak.si/2009/05/wrapping-adn-resizing-gtklabel.html - */ -static void -cb_allocate (GtkWidget *label, GtkAllocation *allocation, gpointer data) -{ - gtk_widget_set_size_request (label, allocation->width - 8, -1); + if (!s->dpms_supported_p) + gtk_frame_set_label (GTK_FRAME (win->dpms_frame), + _("Display Power Management (not supported by this display)")); } -static void -populate_popup_window (state *s) +/* Creates a human-readable anchor to put on a URL. + */ +static char * +anchorize (const char *url) { - GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc")); - char *doc_string = 0; - - /* #### not in Gtk 1.2 - gtk_label_set_selectable (doc); - */ - - g_signal_connect (G_OBJECT (doc), "size-allocate", - G_CALLBACK (cb_allocate), NULL); - -# ifdef HAVE_XML - if (s->cdata) - { - free_conf_data (s->cdata); - s->cdata = 0; + const char *wiki1 = "http://en.wikipedia.org/wiki/"; + const char *wiki2 = "https://en.wikipedia.org/wiki/"; + const char *math1 = "http://mathworld.wolfram.com/"; + const char *math2 = "https://mathworld.wolfram.com/"; + if (!strncmp (wiki1, url, strlen(wiki1)) || + !strncmp (wiki2, url, strlen(wiki2))) { + char *anchor = (char *) malloc (strlen(url) * 3 + 10); + const char *in; + char *out; + strcpy (anchor, "Wikipedia: \""); + in = url + strlen(!strncmp (wiki1, url, strlen(wiki1)) ? wiki1 : wiki2); + out = anchor + strlen(anchor); + while (*in) { + if (*in == '_') { + *out++ = ' '; + } else if (*in == '#') { + *out++ = ':'; + *out++ = ' '; + } else if (*in == '%') { + char hex[3]; + unsigned int n = 0; + hex[0] = in[1]; + hex[1] = in[2]; + hex[2] = 0; + sscanf (hex, "%x", &n); + *out++ = (char) n; + in += 2; + } else { + *out++ = *in; + } + in++; } - - { - 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); + *out++ = '"'; + *out = 0; + return anchor; + + } else if (!strncmp (math1, url, strlen(math1)) || + !strncmp (math2, url, strlen(math2))) { + char *anchor = (char *) malloc (strlen(url) * 3 + 10); + const char *start, *in; + char *out; + strcpy (anchor, "MathWorld: \""); + start = url + strlen(!strncmp (math1, url, strlen(math1)) ? math1 : math2); + in = start; + out = anchor + strlen(anchor); + while (*in) { + if (*in == '_') { + *out++ = ' '; + } else if (in != start && *in >= 'A' && *in <= 'Z') { + *out++ = ' '; + *out++ = *in; + } else if (!strncmp (in, ".htm", 4)) { + break; + } else { + *out++ = *in; } - } - - 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."))); + in++; + } + *out++ = '"'; + *out = 0; + return anchor; - { - GtkWidget *w = name_to_widget (s, "dialog_vbox"); - gtk_widget_hide (w); - gtk_widget_unrealize (w); - gtk_widget_realize (w); - gtk_widget_show (w); + } else { + return strdup (url); } } -static void -sensitize_demo_widgets (state *s, Bool sensitive_p) +/* Quote the text as HTML and make URLs be clickable links. + */ +static char * +hreffify (const char *in) { - const char *names[] = { "demo", "settings", - "cmd_label", "cmd_text", "manual", - "visual", "visual_combo" }; - int i; - for (i = 0; i < countof(names); i++) + char *ret, *out; + if (!in) return 0; + + ret = out = malloc (strlen(in) * 3); + while (*in) { - GtkWidget *w = name_to_widget (s, names[i]); - gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p); + if (!strncmp (in, "http://", 7) || + !strncmp (in, "https://", 8)) + { + char *url, *anchor; + const char *end = in; + while (*end && + *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n') + end++; + + url = (char *) malloc (end - in + 1); + strncpy (url, in, end-in); + url [end-in] = 0; + + anchor = anchorize (url); + + strcpy (out, "<a href=\""); out += strlen (out); + strcpy (out, url); out += strlen (out); + strcpy (out, "\">"); out += strlen (out); + strcpy (out, anchor); out += strlen (out); + strcpy (out, "</a>"); out += strlen (out); + free (url); + free (anchor); + in = end; + } + else if (*in == '<') + { + strcpy (out, "<"); + out += strlen (out); + in++; + } + else if (*in == '>') + { + strcpy (out, ">"); + out += strlen (out); + in++; + } + else if (*in == '&') + { + strcpy (out, "&"); + out += strlen (out); + in++; + } + else + { + *out++ = *in++; + } } + *out = 0; + return ret; } static void sensitize_menu_items (state *s, Bool force_p) { - static Bool running_p = False; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + static Bool running_p = FALSE; static time_t last_checked = 0; time_t now = time ((time_t *) 0); - const char *names[] = { "activate_action", "lock_action", "kill_action", - /* "demo" */ }; - int i; if (force_p || now > last_checked + 10) /* check every 10 seconds */ { @@ -3065,289 +2898,52 @@ sensitize_menu_items (state *s, Bool force_p) last_checked = time ((time_t *) 0); } - for (i = 0; i < countof(names); i++) - { - GtkAction *a = GTK_ACTION (gtk_builder_get_object (s->gtk_ui, names[i])); - gtk_action_set_sensitive (a, 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 = GET_WINDOW (s->toplevel_widget); - 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. - */ -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_BOX_ENTRY (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), - gtk_widget_get_style (w)->font_desc, - gtk_get_default_language ()); - height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) + - pango_font_metrics_get_descent (pain)); -#else /* !HAVE_GTK2 */ - height = w->style->font->ascent + w->style->font->descent; -#endif /* !HAVE_GTK2 */ - - height += leading; - height *= lines; - height += border * 2; - w = GTK_WIDGET (name_to_widget (s, "scroller")); - gtk_widget_set_usize (w, -2, height); - } -} - - -#ifndef HAVE_GTK2 - -/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...) - */ - -static char *up_arrow_xpm[] = { - "15 15 4 1", - " c None", - "- c #FFFFFF", - "+ c #D6D6D6", - "@ c #000000", - - " @ ", - " @ ", - " -+@ ", - " -+@ ", - " -+++@ ", - " -+++@ ", - " -+++++@ ", - " -+++++@ ", - " -+++++++@ ", - " -+++++++@ ", - " -+++++++++@ ", - " -+++++++++@ ", - " -+++++++++++@ ", - " @@@@@@@@@@@@@ ", - " ", - - /* Need these here because gdk_pixmap_create_from_xpm_d() walks off - the end of the array (Gtk 1.2.5.) */ - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" -}; - -static char *down_arrow_xpm[] = { - "15 15 4 1", - " c None", - "- c #FFFFFF", - "+ c #D6D6D6", - "@ c #000000", - - " ", - " ------------- ", - " -+++++++++++@ ", - " -+++++++++@ ", - " -+++++++++@ ", - " -+++++++@ ", - " -+++++++@ ", - " -+++++@ ", - " -+++++@ ", - " -+++@ ", - " -+++@ ", - " -+@ ", - " -+@ ", - " @ ", - " @ ", - - /* Need these here because gdk_pixmap_create_from_xpm_d() walks off - the end of the array (Gtk 1.2.5.) */ - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", - "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" -}; + gtk_widget_set_sensitive (win->activate_menuitem, running_p); + gtk_widget_set_sensitive (win->lock_menuitem, running_p); + gtk_widget_set_sensitive (win->kill_menuitem, running_p); -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); + gtk_menu_item_set_label (GTK_MENU_ITEM (win->restart_menuitem), + (running_p + ? _("Restart Daemon") + : _("Launch Daemon"))); } -#endif /* !HAVE_GTK2 */ - - -#ifndef HAVE_GTK2 -/* Work around a Gtk bug that causes label widgets to wrap text too early. - */ - -static void -you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label, - GtkAllocation *allocation, - void *foo) -{ - GtkRequisition req; - GtkWidgetAuxInfo *aux_info; - - aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info"); - aux_info->width = allocation->width; - aux_info->height = -2; - aux_info->x = -1; - aux_info->y = -1; - gtk_widget_size_request (label, &req); -} - -/* Feel the love. Thanks to Nat Friedman for finding this workaround. +/* Fill in the contents of the main window, and a few things on the + settings dialog. */ 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)); -} -#endif /* !HAVE_GTK2 */ - - -static void populate_demo_window (state *s, int list_elt) { - Display *dpy = GDK_DISPLAY(); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); 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, "opt_frame")); - GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text")); - GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo")); - GtkWidget *list = GTK_WIDGET (name_to_widget (s, "list")); + GtkFrame *frame1 = GTK_FRAME (win->preview_frame); + GtkFrame *frame2 = dialog ? GTK_FRAME (dialog->opt_frame) : 0; + GtkEntry *cmd = dialog ? GTK_ENTRY (dialog->cmd_text) : 0; + GtkComboBox *vis = dialog ? GTK_COMBO_BOX (dialog->visual_combo) : 0; + + /* Enforce a minimum size on the preview pane. */ + if (s->dpy) + { + int dw = DisplayWidth (s->dpy, 0); + int dh = DisplayHeight (s->dpy, 0); + int minw, minh; + # define TRY(W) do { \ + minw = (W); minh = minw * 9/16 + 48; \ + if (dw > minw * 1.5 && dh > minh * 1.5) \ + gtk_widget_set_size_request (GTK_WIDGET (frame1), minw, minh); \ + } while(0) + TRY (300); + TRY (400); + TRY (480); + TRY (640); + TRY (800); + /* TRY (960); */ +# undef TRY + } if (p->mode == BLANK_ONLY) { @@ -3371,7 +2967,7 @@ populate_demo_window (state *s, int list_elt) pretty_name = (hack ? (hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)) + : make_hack_name (s->dpy, hack->command)) : 0); if (hack) @@ -3383,31 +2979,40 @@ 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)); + if (dialog->unedited_cmdline) free (dialog->unedited_cmdline); + dialog->unedited_cmdline = strdup (hack ? hack->command : ""); - gtk_entry_set_text (cmd, (hack ? hack->command : "")); - gtk_entry_set_position (cmd, 0); + gtk_frame_set_label (frame1, _(pretty_name)); + if (frame2) + gtk_frame_set_label (frame2, _(pretty_name)); + if (cmd) + gtk_entry_set_text (cmd, dialog->unedited_cmdline); { char title[255]; sprintf (title, _("%s: %.100s Settings"), progclass, (pretty_name ? pretty_name : "???")); - gtk_window_set_title (GTK_WINDOW (s->popup_widget), title); + gtk_window_set_title (GTK_WINDOW (s->window), title); + if (s->dialog) + gtk_window_set_title (GTK_WINDOW (s->dialog), title); } - gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))), - (hack - ? (hack->visual && *hack->visual - ? hack->visual - : _("Any")) - : "")); + /* Fill in the Visual combo-box */ + if (vis) + gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))), + (hack + ? (hack->visual && *hack->visual + ? hack->visual + : _("Any")) + : "")); - sensitize_demo_widgets (s, (hack ? True : False)); + sensitize_demo_widgets (s, (hack ? TRUE : FALSE)); if (pretty_name) free (pretty_name); - ensure_selected_item_visible (list); + /* This causes the window to scroll out from under the mouse when + clicking on an item, vertically centering it. That's annoying. */ + /* ensure_selected_item_visible (s, list); */ s->_selected_list_element = list_elt; } @@ -3416,19 +3021,7 @@ populate_demo_window (state *s, int 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 } @@ -3452,7 +3045,6 @@ sort_hack_cmp (const void *a, const void *b) static void initialize_sort_map (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; int i, j; @@ -3503,7 +3095,7 @@ initialize_sort_map (state *s) screenhack *hack = p->screenhacks[i]; char *name = (hack->name && *hack->name ? strdup (hack->name) - : make_hack_name (dpy, hack->command)); + : make_hack_name (s->dpy, hack->command)); gchar *s2 = g_str_to_ascii (name, 0); /* Sort "Möbius" properly */ gchar *s3 = g_ascii_strdown (s2, -1); free (name); @@ -3538,13 +3130,13 @@ initialize_sort_map (state *s) static int maybe_reload_init_file (state *s) { - Display *dpy = GDK_DISPLAY(); saver_preferences *p = &s->prefs; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); int status = 0; - static Bool reentrant_lock = False; + static Bool reentrant_lock = FALSE; if (reentrant_lock) return 0; - reentrant_lock = True; + reentrant_lock = TRUE; if (init_file_changed_p (p)) { @@ -3555,120 +3147,106 @@ maybe_reload_init_file (state *s) 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, D_NONE, 100); + sprintf (b, _("file \"%s\" has changed, reloading.\n"), f); + warning_dialog (s->window, _("Warning"), b); free (b); - load_init_file (dpy, p); + load_init_file (s->dpy, p); initialize_sort_map (s); list_elt = selected_list_element (s); - list = name_to_widget (s, "list"); + list = win->list; gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL); populate_hack_list (s); - force_list_select_item (s, list, list_elt, True); + force_list_select_item (s, list, list_elt, TRUE); populate_prefs_page (s); populate_demo_window (s, list_elt); - ensure_selected_item_visible (list); + populate_popup_window (s); + ensure_selected_item_visible (s, list); status = 1; } - reentrant_lock = False; + reentrant_lock = FALSE; return status; } - /* Making the preview window have the right X visual (so that GL works.) */ static Visual *get_best_gl_visual (state *); static GdkVisual * -x_visual_to_gdk_visual (Visual *xv) +x_visual_to_gdk_visual (GdkWindow *win, Visual *xv) { - GList *gvs = gdk_list_visuals(); - if (!xv) return gdk_visual_get_system(); - for (; gvs; gvs = gvs->next) + if (xv) { - GdkVisual *gv = (GdkVisual *) gvs->data; - if (xv == GDK_VISUAL_XVISUAL (gv)) - return gv; + GdkScreen *screen = gdk_window_get_screen (win); + GList *gvs = gdk_screen_list_visuals (screen); + /* This list is sometimes NULL, not even the default visual! */ + 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(); + return 0; } + static void clear_preview_window (state *s) { - GtkWidget *p; - GdkWindow *window; - GtkStyle *style; - - if (!s->toplevel_widget) return; /* very early */ - p = name_to_widget (s, "preview"); - window = GET_WINDOW (p); - - if (!window) return; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->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); + Bool nothing_p = (s->total_available < 5); + + GtkWidget *notebook = win->preview_notebook; + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), + (!s->running_preview_error_p ? 0 : /* ok */ + nothing_p ? 3 : /* no hacks installed */ + !available_p ? 2 : /* hack not installed */ + s->wayland_p ? 4 : /* fucking wayland */ + 1)); /* preview failed */ +} - /* Flush the widget background down into the window, in case a subproc - has changed it. */ - style = gtk_widget_get_style (p); - gdk_window_set_background (window, &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); - Bool nothing_p = (s->total_available < 5); - -#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 : - nothing_p ? 3 : 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 */ - } +static gboolean +preview_resize_cb (GtkWidget *self, GdkEvent *event, gpointer data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (data); + state *s = &win->state; - gdk_flush (); + /* If a subproc is running, clear the window to black when we resize. + Without this, sometimes turds get left behind. */ + if (s->dpy && !s->wayland_p && s->running_preview_cmd) + { + GdkWindow *window = gtk_widget_get_window (self); + Window id; + XWindowAttributes xgwa; + XGCValues gcv; + GC gc; + + if (! window) return TRUE; + id = gdk_x11_window_get_xid (window); + if (! id) return TRUE; + + /* Not sure why XClearWindow is insufficient here, but it is. */ + XGetWindowAttributes (s->dpy, id, &xgwa); + gcv.foreground = BlackPixelOfScreen (xgwa.screen); + gc = XCreateGC (s->dpy, id, GCForeground, &gcv); + XFillRectangle (s->dpy, id, gc, 0, 0, xgwa.width, xgwa.height); + XFreeGC (s->dpy, gc); + } + return FALSE; } @@ -3680,18 +3258,20 @@ reset_preview_window (state *s) 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 (GET_REALIZED (pr)) + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *pr = win->preview; + if (s->dpy && !s->wayland_p && gtk_widget_get_realized (pr)) { - GdkWindow *window = GET_WINDOW (pr); - Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0); + GdkWindow *window = gtk_widget_get_window (pr); + Window oid = (window ? gdk_x11_window_get_xid (window) : 0); Window id; gtk_widget_hide (pr); gtk_widget_unrealize (pr); + gtk_widget_set_has_window (pr, TRUE); gtk_widget_realize (pr); gtk_widget_show (pr); - id = (window ? GDK_WINDOW_XWINDOW (window) : 0); - if (s->debug_p) + id = (window ? gdk_x11_window_get_xid (window) : 0); + if (s->debug_p && oid != id) fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(), (unsigned int) oid, (unsigned int) id); @@ -3699,60 +3279,47 @@ reset_preview_window (state *s) } +/* Make the preview widget use the best GL visual. + We just always use that one rather than switching. + */ 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)); + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + Visual *xvisual = s->gl_visual; + GtkWidget *widget = win->preview; + GdkWindow *gwindow = gtk_widget_get_window (GTK_WIDGET (win)); + GdkScreen *gscreen = gdk_window_get_screen (gwindow); + GdkVisual *gvisual1 = gdk_screen_get_system_visual (gscreen); + GdkVisual *gvisual2 = x_visual_to_gdk_visual (gwindow, xvisual); + + if (! gvisual2) + { + gvisual2 = gvisual1; + if (s->debug_p) + fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual;" + " winging it.\n", blurb(), + (xvisual ? (unsigned long) xvisual->visualid : 0L)); + } if (s->debug_p) fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(), - (visual == dvisual ? "default" : "non-default"), + (gvisual1 == gvisual2 ? "default" : "non-default"), (xvisual ? (unsigned long) xvisual->visualid : 0L)); - if (!GET_REALIZED (widget) || - gtk_widget_get_visual (widget) != visual) + if (!gtk_widget_get_realized (widget) || + gtk_widget_get_visual (widget) != gvisual2) { gtk_widget_unrealize (widget); - gtk_widget_set_visual (widget, visual); - gtk_widget_set_colormap (widget, cmap); + gtk_widget_set_has_window (widget, TRUE); + gtk_widget_set_visual (widget, gvisual2); gtk_widget_realize (widget); } - /* Set the Widget colors to be white-on-black. */ - { - GdkWindow *window = GET_WINDOW (widget); - GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget)); - GdkColormap *cmap = gtk_widget_get_colormap (widget); - GdkColor *fg = &style->fg[GTK_STATE_NORMAL]; - GdkColor *bg = &style->bg[GTK_STATE_NORMAL]; - GdkGC *fgc = gdk_gc_new(window); - GdkGC *bgc = gdk_gc_new(window); - if (!gdk_color_white (cmap, fg)) abort(); - if (!gdk_color_black (cmap, bg)) abort(); - gdk_gc_set_foreground (fgc, fg); - gdk_gc_set_background (fgc, bg); - gdk_gc_set_foreground (bgc, bg); - gdk_gc_set_background (bgc, fg); - style->fg_gc[GTK_STATE_NORMAL] = fgc; - style->bg_gc[GTK_STATE_NORMAL] = fgc; - gtk_widget_set_style (widget, style); - - /* For debugging purposes, put a title on the window (so that - it can be easily found in the output of "xwininfo -tree".) - */ - gdk_window_set_title (window, "Preview"); - } - gtk_widget_show (widget); } - + /* Subprocesses */ @@ -3803,11 +3370,12 @@ reap_zombies (state *s) } +#define EXEC_FAILED_EXIT_STATUS -33 + /* 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; @@ -3838,12 +3406,10 @@ get_best_gl_visual (state *s) } case 0: { - int stdout_fd = 1; - close (in); /* don't need this one */ - close (ConnectionNumber (dpy)); /* close display fd */ + close (ConnectionNumber (s->dpy)); /* close display fd */ - if (dup2 (out, stdout_fd) < 0) /* pipe stdout */ + if (dup2 (out, STDOUT_FILENO) < 0) /* pipe stdout */ { perror ("could not dup() a new stdout:"); return 0; @@ -3865,27 +3431,44 @@ get_best_gl_visual (state *s) 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 */ + _exit (EXEC_FAILED_EXIT_STATUS); /* exits fork */ break; } default: { int result = 0; int wait_status = 0; + int exit_status = EXEC_FAILED_EXIT_STATUS; FILE *f = fdopen (in, "r"); unsigned int v = 0; char c; + int i = 0; close (out); /* don't need this one */ *buf = 0; - if (!fgets (buf, sizeof(buf)-1, f)) - *buf = 0; + do { + errno = 0; + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; + } while (errno == EINTR && /* fgets might fail due to SIGCHLD. */ + i++ < 1000); /* And just in case. */ + fclose (f); /* Wait for the child to die. */ - waitpid (-1, &wait_status, 0); + waitpid (forked, &wait_status, 0); + + exit_status = WEXITSTATUS (wait_status); + /* Treat exit code as a signed 8-bit quantity. */ + if (exit_status & 0x80) exit_status |= ~0xFF; + + if (exit_status == EXEC_FAILED_EXIT_STATUS) + { + fprintf (stderr, "%s: %s is not installed\n", blurb(), av[0]); + return 0; + } if (1 == sscanf (buf, "0x%x %c", &v, &c)) result = (int) v; @@ -3899,10 +3482,9 @@ get_best_gl_visual (state *s) } else { - Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result); + Visual *v = id_to_visual (DefaultScreenOfDisplay (s->dpy), result); if (s->debug_p) - fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n", - blurb(), av[0], result); + fprintf (stderr, "%s: GL visual is 0x%X\n", blurb(), result); if (!v) abort(); return v; } @@ -3916,14 +3498,14 @@ get_best_gl_visual (state *s) static void kill_preview_subproc (state *s, Bool reset_p) { - s->running_preview_error_p = False; + 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); + g_source_remove (s->subproc_check_timer_id); s->subproc_check_timer_id = 0; s->subproc_check_countdown = 0; } @@ -3979,30 +3561,33 @@ kill_preview_subproc (state *s, Bool reset_p) static void launch_preview_subproc (state *s) { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); 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"); + GtkWidget *pr = win->preview; GdkWindow *window; reset_preview_window (s); - window = GET_WINDOW (pr); + window = gtk_widget_get_window (pr); - s->running_preview_error_p = False; + s->running_preview_error_p = FALSE; - if (s->preview_suppressed_p) + if (s->preview_suppressed_p || !s->gl_visual) { - kill_preview_subproc (s, False); + kill_preview_subproc (s, FALSE); goto DONE; } new_cmd = malloc (strlen (cmd) + 40); - id = (window ? GDK_WINDOW_XWINDOW (window) : 0); + id = (window && !s->wayland_p + ? gdk_x11_window_get_xid (window) + : 0); if (id == 0) { /* No window id? No command to run. */ @@ -4012,19 +3597,22 @@ launch_preview_subproc (state *s) else { /* We do this instead of relying on $XSCREENSAVER_WINDOW specifically - so that third-party savers that don't implement -window-id will fail: + so that third-party savers that don't implement --window-id will fail: otherwise we might have full-screen windows popping up when we were just trying to get a preview thumbnail. */ strcpy (new_cmd, cmd); - sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", + sprintf (new_cmd + strlen (new_cmd), " --window-id 0x%X", (unsigned int) id); } - kill_preview_subproc (s, False); + if (id && s->screenshot) + screenshot_save (s->dpy, id, s->screenshot); + + kill_preview_subproc (s, FALSE); if (! new_cmd) { - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; clear_preview_window (s); goto DONE; } @@ -4036,13 +3624,13 @@ launch_preview_subproc (state *s) char buf[255]; sprintf (buf, "%s: couldn't fork", blurb()); perror (buf); - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; goto DONE; break; } case 0: { - close (ConnectionNumber (GDK_DISPLAY())); + close (ConnectionNumber (s->dpy)); hack_subproc_environment (id, s->debug_p); @@ -4081,6 +3669,16 @@ launch_preview_subproc (state *s) } } + /* Put some props on the embedded preview window, for debugging. */ + XStoreName (s->dpy, id, "XScreenSaver Settings Preview"); + XChangeProperty (s->dpy, id, XA_WM_COMMAND, + XA_STRING, 8, PropModeReplace, + (unsigned char *) new_cmd, + strlen (new_cmd)); + XChangeProperty (s->dpy, id, XA_NET_WM_PID, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &forked, 1); + schedule_preview_check (s); DONE: @@ -4101,8 +3699,7 @@ hack_environment (state *s) ""; # endif - Display *dpy = GDK_DISPLAY(); - const char *odpy = DisplayString (dpy); + const char *odpy = s->dpy ? DisplayString (s->dpy) : ":0.0"; char *ndpy = (char *) malloc(strlen(odpy) + 20); strcpy (ndpy, "DISPLAY="); strcat (ndpy, odpy); @@ -4141,7 +3738,7 @@ 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, + them with "--root" and not with "--window-id" -- which, of course, doesn't happen. */ char *nssw = (char *) malloc (40); @@ -4159,6 +3756,7 @@ hack_subproc_environment (Window preview_window_id, Bool debug_p) } + /* Called from a timer: Launches the currently-chosen subprocess, if it's not already running. If there's a different process running, kills it. @@ -4168,7 +3766,7 @@ update_subproc_timer (gpointer data) { state *s = (state *) data; if (! s->desired_preview_cmd) - kill_preview_subproc (s, True); + kill_preview_subproc (s, TRUE); else if (!s->running_preview_cmd || !!strcmp (s->desired_preview_cmd, s->running_preview_cmd)) launch_preview_subproc (s); @@ -4177,13 +3775,6 @@ update_subproc_timer (gpointer data) return FALSE; /* do not re-execute timer */ } -static int -settings_timer (gpointer data) -{ - settings_cb (0, 0); - return FALSE; -} - /* Call this when you think you might want a preview process running. It will set a timer that will actually launch that program a second @@ -4207,8 +3798,8 @@ schedule_preview (state *s, const char *cmd) s->desired_preview_cmd = (cmd ? strdup (cmd) : 0); if (s->subproc_timer_id) - gtk_timeout_remove (s->subproc_timer_id); - s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s); + g_source_remove (s->subproc_timer_id); + s->subproc_timer_id = g_timeout_add (delay, update_subproc_timer, s); } @@ -4219,12 +3810,12 @@ static int check_subproc_timer (gpointer data) { state *s = (state *) data; - Bool again_p = True; + Bool again_p = TRUE; if (s->running_preview_error_p || /* already dead */ s->running_preview_pid <= 0) { - again_p = False; + again_p = FALSE; } else { @@ -4232,9 +3823,9 @@ check_subproc_timer (gpointer data) reap_zombies (s); status = kill (s->running_preview_pid, 0); if (status < 0 && errno == ESRCH) - s->running_preview_error_p = True; + s->running_preview_error_p = TRUE; - if (s->debug_p) + if (s->debug_p && s->running_preview_error_p) { char *ss = subproc_pretty_name (s); fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(), @@ -4246,7 +3837,7 @@ check_subproc_timer (gpointer data) if (s->running_preview_error_p) { clear_preview_window (s); - again_p = False; + again_p = FALSE; } } @@ -4254,7 +3845,7 @@ check_subproc_timer (gpointer data) might be satisfied. */ if (--s->subproc_check_countdown <= 0) - again_p = False; + again_p = FALSE; if (again_p) return TRUE; /* re-execute timer */ @@ -4272,7 +3863,7 @@ check_subproc_timer (gpointer data) check whether the program is still running. The assumption here is that if the process didn't stay up for more than a couple of seconds, then either the program doesn't exist, or it doesn't - take a -window-id argument. + take a --window-id argument. */ static void schedule_preview_check (state *s) @@ -4284,27 +3875,27 @@ schedule_preview_check (state *s) fprintf (stderr, "%s: scheduling check\n", blurb()); if (s->subproc_check_timer_id) - gtk_timeout_remove (s->subproc_check_timer_id); + g_source_remove (s->subproc_check_timer_id); s->subproc_check_timer_id = - gtk_timeout_add (1000 / ticks, - check_subproc_timer, (gpointer) s); + g_timeout_add (1000 / ticks, + check_subproc_timer, (gpointer) s); s->subproc_check_countdown = ticks * seconds; } static Bool -screen_blanked_p (void) +screen_blanked_p (state *s) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *dataP = 0; - Display *dpy = GDK_DISPLAY(); - Bool blanked_p = False; + Bool blanked_p = FALSE; - if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */ + if (!s->dpy) return FALSE; + if (XGetWindowProperty (s->dpy, RootWindow (s->dpy, 0), /* always screen 0 */ XA_SCREENSAVER_STATUS, - 0, 3, False, XA_INTEGER, + 0, 3, FALSE, XA_INTEGER, &type, &format, &nitems, &bytesafter, &dataP) == Success @@ -4329,63 +3920,45 @@ static int check_blanked_timer (gpointer data) { state *s = (state *) data; - Bool blanked_p = screen_blanked_p (); + Bool blanked_p = screen_blanked_p (s); if (blanked_p && s->running_preview_pid) { if (s->debug_p) fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb()); - kill_preview_subproc (s, True); + kill_preview_subproc (s, TRUE); } - return True; /* re-execute timer */ + return TRUE; /* re-execute timer */ } -/* How many screens are there (including Xinerama.) - */ -static int -screen_count (Display *dpy) +/* Is there more than one active monitor? */ +static Bool +multi_screen_p (Display *dpy) { - int nscreens = ScreenCount(dpy); -# ifdef HAVE_XINERAMA - if (nscreens <= 1) + monitor **monitors = dpy ? scan_monitors (dpy) : NULL; + Bool ret = FALSE; + if (monitors) { - int event_number, error_number; - if (XineramaQueryExtension (dpy, &event_number, &error_number) && - XineramaIsActive (dpy)) + int count = 0; + int good_count = 0; + while (monitors[count]) { - XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens); - if (xsi) XFree (xsi); + if (monitors[count]->sanity == S_SANE) + good_count++; + count++; } + free_monitors (monitors); + ret = (good_count > 1); } -# endif /* HAVE_XINERAMA */ - - return nscreens; -} - - -/* Setting window manager icon - */ - -static void -init_icon (GdkWindow *window) -{ - GdkBitmap *mask = 0; - GdkPixmap *pixmap = - gdk_pixmap_create_from_xpm_d (window, &mask, 0, - (gchar **) logo_50_xpm); - if (pixmap) - gdk_window_set_icon (window, 0, pixmap, mask); + return ret; } - -/* The main demo-mode command loop. - */ #if 0 static Bool -mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, - XrmRepresentation *type, XrmValue *value, XPointer closure) +xrm_mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, + XrmRepresentation *type, XrmValue *value, XPointer closure) { int i; for (i = 0; quarks[i]; i++) @@ -4401,98 +3974,146 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks, fprintf (stderr, ": %s\n", (char *) value->addr); - return False; + return FALSE; +} +#endif /* 0 */ + + +static int +ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) +{ + return 0; } -#endif static Window -gnome_screensaver_window (Screen *screen) +gnome_screensaver_window (Display *dpy, char **name_ret) { - Display *dpy = DisplayOfScreen (screen); - Window root = RootWindowOfScreen (screen); - Window parent, *kids; - unsigned int nkids; + int nscreens; + int i, screen; Window gnome_window = 0; - int i; + XErrorHandler old_handler; - if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids)) - abort (); - for (i = 0; i < nkids; i++) + if (!dpy) return 0; + XSync (dpy, FALSE); + old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + + nscreens = ScreenCount (dpy); + for (screen = 0; screen < nscreens; screen++) { - Atom type; - int format; - unsigned long nitems, bytesafter; - unsigned char *name; - if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128, - False, XA_STRING, &type, &format, &nitems, - &bytesafter, &name) - == Success - && type != None - && (!strcmp ((char *) name, "gnome-screensaver") || - !strcmp ((char *) name, "mate-screensaver") || - !strcmp ((char *) name, "cinnamon-screensaver"))) - { - gnome_window = kids[i]; - break; - } + Window root = RootWindow (dpy, screen); + Window parent, *kids; + unsigned int nkids; + + if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids)) + abort (); + if (name_ret) + *name_ret = 0; + for (i = 0; i < nkids; i++) + { + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *name; + if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128, + FALSE, XA_STRING, &type, &format, &nitems, + &bytesafter, &name) + == Success + && type != None + && (!strcmp ((char *) name, "gnome-screensaver") || + !strcmp ((char *) name, "mate-screensaver") || + !strcmp ((char *) name, "cinnamon-screensaver") || + !strcmp ((char *) name, "xfce4-screensaver") || + !strcmp ((char *) name, "light-locker"))) + { + gnome_window = kids[i]; + if (name_ret) + *name_ret = strdup ((char *) name); + break; + } + } + if (kids) XFree ((char *) kids); + if (gnome_window) + break; } - if (kids) XFree ((char *) kids); + XSync (dpy, FALSE); + XSetErrorHandler (old_handler); return gnome_window; } + static Bool -gnome_screensaver_active_p (void) +gnome_screensaver_active_p (state *s, char **name_ret) { - Display *dpy = GDK_DISPLAY(); - Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); - return (w ? True : False); + Window w = gnome_screensaver_window (s->dpy, name_ret); + return (w ? TRUE : FALSE); } + static void -kill_gnome_screensaver (void) +kill_gnome_screensaver (state *s) { - Display *dpy = GDK_DISPLAY(); - Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy)); - if (w) XKillClient (dpy, (XID) w); + Window w = gnome_screensaver_window (s->dpy, NULL); + if (w) XKillClient (s->dpy, (XID) w); } + static Bool kde_screensaver_active_p (void) { + /* Apparently this worked in KDE 3, but not 4 or 5. + Maybe parsing the output of this would work in KDE 5: + kreadconfig5 --file kscreenlockerrc --group Daemon --key Autolock + but there's probably no way to kill the KDE saver. + Fuck it. */ FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null", "r"); char buf[255]; - if (!p) return False; - if (!fgets (buf, sizeof(buf)-1, p)) return False; + int i = 0; + if (!p) return FALSE; + + *buf = 0; + do { + errno = 0; + if (! fgets (buf, sizeof(buf)-1, p)) + *buf = 0; + } while (errno == EINTR && /* fgets might fail due to SIGCHLD. */ + i++ < 1000); /* And just in case. */ + pclose (p); if (!strcmp (buf, "true\n")) - return True; + return TRUE; else - return False; + return FALSE; } + static void -kill_kde_screensaver (void) +kill_kde_screensaver (state *s) { - /* Use empty body to kill warning from gcc -Wall with - "warning: ignoring return value of 'system', - declared with attribute warn_unused_result" - */ - if (system ("dcop kdesktop KScreensaverIface enable false")) {} + int ac = 0; + char *av[10]; + av[ac++] = "dcop"; + av[ac++] = "kdesktop"; + av[ac++] = "KScreensaverIface"; + av[ac++] = "enable"; + av[ac++] = "false"; + av[ac] = 0; + fork_and_exec (s, ac, av); } -static void -the_network_is_not_the_computer (state *s) +static int +the_network_is_not_the_computer (gpointer data) { - Display *dpy = GDK_DISPLAY(); + state *s = (state *) data; char *rversion = 0, *ruser = 0, *rhost = 0; char *luser, *lhost; char *msg = 0; + char *oname = 0; struct passwd *p = getpwuid (getuid ()); - const char *d = DisplayString (dpy); + const char *d = s->dpy ? DisplayString (s->dpy) : ":0.0"; # if defined(HAVE_UNAME) struct utsname uts; @@ -4509,7 +4130,8 @@ the_network_is_not_the_computer (state *s) else luser = "???"; - server_xscreensaver_version (dpy, &rversion, &ruser, &rhost); + if (s->dpy) + server_xscreensaver_version (s->dpy, &rversion, &ruser, &rhost); /* Make a buffer that's big enough for a number of copies of all the strings, plus some. */ @@ -4522,22 +4144,22 @@ the_network_is_not_the_computer (state *s) 1024)); *msg = 0; - if (!rversion || !*rversion) + if ((!rversion || !*rversion) && !s->debug_p) { +# ifndef __APPLE__ sprintf (msg, - _("Warning:\n\n" - "The XScreenSaver daemon doesn't seem to be running\n" - "on display \"%s\". Launch it now?"), + _("The XScreenSaver daemon doesn't seem to be running\n" + "on display \"%.25s\". Launch it now?"), d); +# endif } else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name)) { /* Warn that the two processes are running as different users. */ sprintf(msg, - _("Warning:\n\n" - "%s is running as user \"%s\" on host \"%s\".\n" - "But the xscreensaver managing display \"%s\"\n" + _("%s is running as user \"%s\" on host \"%s\".\n" + "But the xscreensaver managing display \"%.25s\"\n" "is running as user \"%s\" on host \"%s\".\n" "\n" "Since they are different users, they won't be reading/writing\n" @@ -4560,8 +4182,7 @@ the_network_is_not_the_computer (state *s) /* Warn that the two processes are running on different hosts. */ sprintf (msg, - _("Warning:\n\n" - "%s is running as user \"%s\" on host \"%s\".\n" + _("%s is running as user \"%s\" on host \"%s\".\n" "But the xscreensaver managing display \"%s\"\n" "is running as user \"%s\" on host \"%s\".\n" "\n" @@ -4577,13 +4198,12 @@ the_network_is_not_the_computer (state *s) progname, lhost, luser); } - else if (!!strcmp (rversion, s->short_version)) + else if (rversion && *rversion && !!strcmp (rversion, s->short_version)) { /* Warn that the version numbers don't match. */ sprintf (msg, - _("Warning:\n\n" - "This is %s version %s.\n" + _("This is %s version %s.\n" "But the xscreensaver managing display \"%s\"\n" "is version %s. This could cause problems.\n" "\n" @@ -4593,9 +4213,10 @@ the_network_is_not_the_computer (state *s) rversion); } + validate_image_directory_quick (s); if (*msg) - warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1); + warning_dialog_1 (s->window, _("Warning"), msg, D_LAUNCH); if (rversion) free (rversion); if (ruser) free (ruser); @@ -4608,23 +4229,42 @@ the_network_is_not_the_computer (state *s) running" dialog so that these are on top. Good enough. */ - if (gnome_screensaver_active_p ()) - warning_dialog (s->toplevel_widget, - _("Warning:\n\n" - "The GNOME screensaver daemon appears to be running.\n" - "It must be stopped for XScreenSaver to work properly.\n" - "\n" - "Stop the GNOME screen saver daemon now?\n"), - D_GNOME, 1); + if (gnome_screensaver_active_p (s, &oname)) + { + char msg [1024]; + sprintf (msg, + _("The GNOME screen saver daemon (%s) appears to be running.\n" + "It must be stopped for XScreenSaver to work properly.\n" + "\n" + "Stop the \"%s\" daemon now?\n"), + oname, oname); + warning_dialog_1 (s->window, _("Warning"), msg, D_GNOME); + } - if (kde_screensaver_active_p ()) - warning_dialog (s->toplevel_widget, - _("Warning:\n\n" - "The KDE screen saver daemon appears to be running.\n" + if (kde_screensaver_active_p()) + warning_dialog_1 (s->window, _("Warning"), + _("The KDE screen saver daemon appears to be running.\n" "It must be stopped for XScreenSaver to work properly.\n" "\n" "Stop the KDE screen saver daemon now?\n"), - D_KDE, 1); + D_KDE); + + if (! s->gl_visual) + warning_dialog (s->window, _("Error"), + _("No GL visuals: the xscreensaver-gl* packages are required.")); + + if (s->wayland_p) + warning_dialog (s->window, _("Warning"), + _("You are running Wayland rather than the X Window System.\n" + "\n" + "Under Wayland, idle-detection fails when non-X11 programs\n" + "are selected, meaning the screen may blank prematurely.\n" + "Also, locking is impossible.\n" + "\n" + "See the XScreenSaver manual for instructions on\n" + "configuring your system to use X11 instead of Wayland.\n")); + + return FALSE; /* Only run timer once */ } @@ -4632,739 +4272,1155 @@ the_network_is_not_the_computer (state *s) of the program that generated them. */ static int -demo_ehandler (Display *dpy, XErrorEvent *error) +x_error (Display *dpy, XErrorEvent *error) { - state *s = global_state_kludge; /* I hate C so much... */ - fprintf (stderr, "\nX error in %s:\n", blurb()); + fprintf (stderr, "\n%s: X error:\n", blurb()); XmuPrintDefaultErrorMessage (dpy, error, stderr); - kill_preview_subproc (s, False); - exit (-1); + /* No way to get 'state' in here... */ + /* kill_preview_subproc (s, FALSE); */ + exit (-1); /* Likewise, no way to call g_application_quit(). */ return 0; } -/* We use this error handler so that Gtk/Gdk errors are preceeded by the name - of the program that generated them; and also that we can ignore one - particular bogus error message that Gdk madly spews. - */ static void -g_log_handler (const gchar *log_domain, GLogLevelFlags log_level, - const gchar *message, gpointer user_data) -{ - /* Ignore the message "Got event for unknown window: 0x...". - Apparently some events are coming in for the xscreensaver window - (presumably reply events related to the ClientMessage) and Gdk - feels the need to complain about them. So, just suppress any - messages that look like that one. - */ - if (strstr (message, "unknown window")) - return; +g_logger (const gchar *domain, GLogLevelFlags log_level, + const gchar *message, gpointer data) +{ + if (log_level & G_LOG_LEVEL_DEBUG) return; + if (log_level & G_LOG_LEVEL_INFO) return; + fprintf (stderr, "%s: %s: %s\n", blurb(), domain, message); + if (log_level & G_LOG_LEVEL_CRITICAL) exit (-1); +} - fprintf (stderr, "%s: %s-%s: %s%s", blurb(), - (log_domain ? log_domain : progclass), - (log_level == G_LOG_LEVEL_ERROR ? "error" : - log_level == G_LOG_LEVEL_CRITICAL ? "critical" : - log_level == G_LOG_LEVEL_WARNING ? "warning" : - log_level == G_LOG_LEVEL_MESSAGE ? "message" : - log_level == G_LOG_LEVEL_INFO ? "info" : - log_level == G_LOG_LEVEL_DEBUG ? "debug" : "???"), - message, - ((!*message || message[strlen(message)-1] != '\n') - ? "\n" : "")); +/* Why are there two of these hooks and why does this one suck so hard?? */ +static GLogWriterOutput +g_other_logger (GLogLevelFlags log_level, const GLogField *fields, + gsize n_fields, gpointer data) +{ + int i; + GLogWriterOutput ret = G_LOG_WRITER_UNHANDLED; + if (log_level & G_LOG_LEVEL_DEBUG) return ret; + if (log_level & G_LOG_LEVEL_INFO) return ret; + for (i = 0; i < n_fields; i++) + { + const GLogField *field = &fields[i]; + if (strcmp (field->key, "MESSAGE")) continue; + fprintf (stderr, "%s: %s\n", blurb(), (char *) field->value); + ret = G_LOG_WRITER_HANDLED; + } + if (log_level & G_LOG_LEVEL_CRITICAL) exit (-1); + return ret; } -STFU -static char *defaults[] = { -#include "XScreenSaver_ad.h" - 0 -}; +/**************************************************************************** -#if 0 -#ifdef HAVE_CRAPPLET -static struct poptOption crapplet_options[] = { - {NULL, '\0', 0, NULL, 0} -}; -#endif /* HAVE_CRAPPLET */ -#endif /* 0 */ + XScreenSaverDialog callbacks, referenced by prefs.ui. -const char *usage = "[--display dpy] [--prefs | --settings]" -# ifdef HAVE_CRAPPLET - " [--crapplet]" -# endif - "\n\t\t [--debug] [--sync] [--no-xshm] [--configdir dir]"; + ****************************************************************************/ -static void -map_popup_window_cb (GtkWidget *w, gpointer user_data) +/* The "Documentation" button on the Settings dialog */ +G_MODULE_EXPORT void +manual_cb (GtkButton *button, 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; -} + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + saver_preferences *p = &s->prefs; + GtkWidget *list_widget = win->list; + int list_elt = selected_list_element (s); + int hack_number; + char *name, *name2, *cmd, *str; + char *oname = 0; + if (s->debug_p) fprintf (stderr, "%s: documentation button\n", blurb()); + 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 (s, list_widget); -#if 0 -static void -print_widget_tree (GtkWidget *w, int depth) -{ - int i; - for (i = 0; i < depth; i++) - fprintf (stderr, " "); - fprintf (stderr, "%s\n", gtk_widget_get_name (w)); + 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) name2 = str+1; - if (GTK_IS_LIST (w)) + cmd = get_string_resource (s->dpy, "manualCommand", "ManualCommand"); + if (cmd) { - for (i = 0; i < depth+1; i++) - fprintf (stderr, " "); - fprintf (stderr, "...list kids...\n"); + int ac = 0; + char *av[10]; + char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100); + sprintf (cmd2, cmd, name2, name2, name2, name2); + av[ac++] = "/bin/sh"; + av[ac++] = "-c"; + av[ac++] = cmd2; + av[ac] = 0; + fork_and_exec (s, ac, av); + free (cmd2); } - else if (GTK_IS_CONTAINER (w)) + else { - GList *kids = gtk_container_children (GTK_CONTAINER (w)); - while (kids) - { - print_widget_tree (GTK_WIDGET (kids->data), depth+1); - kids = kids->next; - } + warning_dialog (s->window, _("Error"), + _("no `manualCommand' resource set.")); } + + free (oname); } -#endif /* 0 */ -static int -delayed_scroll_kludge (gpointer data) + +static void +settings_sync_cmd_text (state *s) { - state *s = (state *) data; - GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list")); - ensure_selected_item_visible (w); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + char *cmd_line; + if (! s->cdata) return; + cmd_line = get_configurator_command_line (s->cdata, FALSE); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + free (cmd_line); +} - /* Oh, this is just fucking lovely, too. */ - w = GTK_WIDGET (name_to_widget (s, "preview")); - gtk_widget_hide (w); - gtk_widget_show (w); - return FALSE; /* do not re-execute timer */ +/* The "Advanced" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_adv_cb (GtkButton *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + if (s->debug_p) fprintf (stderr, "%s: settings advanced button\n", blurb()); + settings_sync_cmd_text (s); + gtk_notebook_set_current_page (notebook, 1); } -#ifdef HAVE_GTK2 -static GtkWidget * -create_xscreensaver_demo (void) +/* The "Standard" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_std_cb (GtkButton *button, gpointer user_data) { - GtkWidget *nb; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + if (s->debug_p) fprintf (stderr, "%s: settings standard button\n", blurb()); + settings_sync_cmd_text (s); + gtk_notebook_set_current_page (notebook, 0); +} - nb = name_to_widget (global_state_kludge, "preview_notebook"); - gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE); - return name_to_widget (global_state_kludge, "xscreensaver_demo"); +/* The "Reset to Defaults" button on the settings dialog. */ +G_MODULE_EXPORT void +settings_reset_cb (GtkButton *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + state *s = &win->state; + char *cmd_line; + if (s->debug_p) fprintf (stderr, "%s: settings reset button\n", blurb()); + if (! s->cdata) return; + cmd_line = get_configurator_command_line (s->cdata, TRUE); + gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line); + free (cmd_line); + populate_popup_window (s); } -static GtkWidget * -create_xscreensaver_settings_dialog (void) + +/* Called when "Advanced/Standard" buttons change the displayed page. */ +G_MODULE_EXPORT void +settings_switch_page_cb (GtkNotebook *notebook, GtkWidget *page, + gint page_num, gpointer user_data) { - GtkWidget *w, *box; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkWidget *adv = dialog->adv_button; + GtkWidget *std = dialog->std_button; + if (s->debug_p) fprintf (stderr, "%s: settings page changed\n", blurb()); - box = name_to_widget (global_state_kludge, "dialog_action_area"); + if (page_num == 0) + { + gtk_widget_show (adv); + gtk_widget_hide (std); + } + else if (page_num == 1) + { + gtk_widget_hide (adv); + gtk_widget_show (std); + } + else + abort(); - w = name_to_widget (global_state_kludge, "adv_button"); - gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); + /* Nobody uses the "Advanced" tab. Let's just hide it. + (The tab still needs to be there, since the 'cmd_text' widget is + what gets stored into the .xscreensaver file.) */ + gtk_widget_hide (adv); + gtk_widget_hide (std); +} - w = name_to_widget (global_state_kludge, "std_button"); - gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE); - return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog"); +/* The "Cancel" button on the Settings dialog. */ +G_MODULE_EXPORT void +settings_cancel_cb (GtkWidget *button, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + if (s->debug_p) fprintf (stderr, "%s: settings cancel button\n", blurb()); + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_unrealize (GTK_WIDGET (dialog)); + + /* Restart the hack running in the Preview window with the reset options. */ + schedule_preview (s, dialog->unedited_cmdline); } -#endif /* HAVE_GTK2 */ -int -main (int argc, char **argv) +/* The "Ok" button on the Settings dialog. */ +G_MODULE_EXPORT void +settings_ok_cb (GtkWidget *button, gpointer user_data) { - XtAppContext app; - state S, *s; - saver_preferences *p; - Bool prefs_p = False; - Bool settings_p = False; - int i; - Display *dpy; - Widget toplevel_shell; - char *real_progname = argv[0]; - char *window_title; - char *geom = 0; - Bool crapplet_p = False; - char *str; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (user_data); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + GtkNotebook *notebook = GTK_NOTEBOOK (dialog->opt_notebook); + int page = gtk_notebook_get_current_page (notebook); + if (s->debug_p) fprintf (stderr, "%s: settings ok button\n", blurb()); -#ifdef ENABLE_NLS - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - textdomain (GETTEXT_PACKAGE); + if (page == 0) + /* Regenerate the command-line from the widget contents before saving. + But don't do this if we're looking at the command-line page already, + or we will blow away what they typed... */ + settings_sync_cmd_text (s); -# ifdef HAVE_GTK2 - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); -# else /* !HAVE_GTK2 */ - if (!setlocale (LC_ALL, "")) - fprintf (stderr, "%s: locale not supported by C library\n", real_progname); -# endif /* !HAVE_GTK2 */ + flush_popup_changes_and_save (s); + gtk_widget_hide (GTK_WIDGET (dialog)); + gtk_widget_unrealize (GTK_WIDGET (dialog)); +} -#endif /* ENABLE_NLS */ - str = strrchr (real_progname, '/'); - if (str) real_progname = str+1; +/* Called when any widget value is changed in the Settings dialog. */ +static void +dialog_change_cb (GtkWidget *widget, gpointer user_data) +{ + state *s = (state *) user_data; + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + char *cur_cmd, *def_cmd; + const char *prev_cmd; + if (!dialog || !s->cdata) return; + settings_sync_cmd_text (s); + cur_cmd = get_configurator_command_line (s->cdata, FALSE); + def_cmd = get_configurator_command_line (s->cdata, TRUE); + prev_cmd = dialog->unedited_cmdline; - s = &S; - memset (s, 0, sizeof(*s)); - s->initializing_p = True; - p = &s->prefs; + /* "Reset to Defaults" button enabled only if current cmd is not default. */ + gtk_widget_set_sensitive (dialog->reset_button, + !!strcmp (cur_cmd, def_cmd)); - global_state_kludge = s; /* I hate C so much... */ + /* "Save" button enabled only if current cmd is edited. */ + gtk_widget_set_sensitive (dialog->ok_button, + !!strcmp (cur_cmd, prev_cmd)); - progname = real_progname; + /* Restart the hack running in the Preview window with the prevailing, + un-saved set of options, for a realtime preview of what they do. */ + schedule_preview (s, cur_cmd); - s->short_version = XSCREENSAVER_VERSION; + free (cur_cmd); + free (def_cmd); +} + + +/* Fill in the contents of the "Settings" dialog for the current hack. + It may or may not currently be visible. + */ +static void +populate_popup_window (state *s) +{ + saver_preferences *p = &s->prefs; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + + GtkLabel *doc = dialog ? GTK_LABEL (dialog->doc) : 0; + char *doc_string = 0; + + if (s->cdata) + { + free_conf_data (s->cdata); + s->cdata = 0; + } - /* Register our error message logger for every ``log domain'' known. - There's no way to do this globally, so I grepped the Gtk/Gdk sources - for all of the domains that seem to be in use. - */ { - const char * const domains[] = { 0, - "Gtk", "Gdk", "GLib", "GModule", - "GThread", "Gnome", "GnomeUI" }; - for (i = 0; i < countof(domains); i++) - g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0); + 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 (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + hack = 0; + + if (hack && dialog) + { + GtkWidget *parent = dialog->settings_vbox; + GtkWidget *cmd = GTK_WIDGET (dialog->cmd_text); + const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd)); + if (!cmd_line) abort(); + s->cdata = load_configurator (cmd_line, dialog_change_cb, s, + s->debug_p); + dialog_change_cb (NULL, s); + if (s->cdata && s->cdata->widget) + gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, + TRUE, TRUE, 0); + + /* Make the pretty name on the tab boxes include the year and be bold. + */ + if (s->cdata && s->cdata->year) + { + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkFrame *frame1 = GTK_FRAME (win->preview_frame); + GtkFrame *frame2 = GTK_FRAME (dialog->opt_frame); + char *pretty_name = (hack->name + ? strdup (hack->name) + : make_hack_name (s->dpy, hack->command)); + char *s2 = (char *) malloc (strlen (pretty_name) + 20); + sprintf (s2, "<b>%s (%d)</b>", pretty_name, s->cdata->year); + free (pretty_name); + pretty_name = s2; + + gtk_frame_set_label (frame1, _(pretty_name)); + gtk_frame_set_label (frame2, _(pretty_name)); + gtk_label_set_use_markup ( /* Must be after set_label */ + GTK_LABEL (gtk_frame_get_label_widget (frame1)), TRUE); + gtk_label_set_use_markup ( + GTK_LABEL (gtk_frame_get_label_widget (frame2)), TRUE); + free (pretty_name); + } + } } -#ifdef DEFAULT_ICONDIR /* from -D on compile line */ -# ifndef HAVE_GTK2 + doc_string = (s->cdata && s->cdata->description && *s->cdata->description + ? _(s->cdata->description) + : 0); + doc_string = hreffify (doc_string); + if (doc) + { + GtkWidget *w = dialog->dialog_vbox; + gtk_label_set_text (doc, (doc_string + ? doc_string + : _("No description available."))); + gtk_label_set_use_markup (doc, TRUE); + + /* Shrink the dialog to the minimum viable size. */ + gtk_window_resize (GTK_WINDOW (dialog), 1, 1); + + gtk_widget_hide (w); + gtk_widget_unrealize (w); + gtk_widget_realize (w); + gtk_widget_show (w); + } + + /* Also set the documentation on the main window, below the preview. */ { - const char *dir = DEFAULT_ICONDIR; - if (*dir) add_pixmap_directory (dir); + GtkLabel *doc2 = GTK_LABEL (win->short_preview_label); + GtkLabel *doc3 = GTK_LABEL (win->preview_author_label); + char *s2 = 0; + char *s3 = 0; + + if (doc_string) + { + /* Keep only the first paragraph, and the last line. + Omit everything in between. */ + const char *second_para = strstr (doc_string, "\n\n"); + const char *last_line = strrchr (doc_string, '\n'); + s2 = strdup (doc_string); + if (second_para) + s2[second_para - doc_string] = 0; + if (last_line) + s3 = strdup (last_line + 1); + } + + gtk_label_set_text (doc2, + (s2 + ? _(s2) + : (p->mode == BLANK_ONLY || p->mode == DONT_BLANK) + ? "" + : _("No description available."))); + gtk_label_set_text (doc3, (s3 ? _(s3) : "")); + if (s2) free (s2); + if (s3) free (s3); } -# endif /* !HAVE_GTK2 */ -#endif /* DEFAULT_ICONDIR */ - /* This is gross, but Gtk understands --display and not -display... - */ - for (i = 1; i < argc; i++) - if (argv[i][0] && argv[i][1] && - !strncmp(argv[i], "-display", strlen(argv[i]))) - argv[i] = "--display"; + if (doc_string) + free (doc_string); +} - /* We need to parse this arg really early... Sigh. */ - for (i = 1; i < argc; i++) +static void +sensitize_demo_widgets (state *s, Bool sensitive_p) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + gtk_widget_set_sensitive (win->demo, sensitive_p); + gtk_widget_set_sensitive (win->settings, sensitive_p); + if (dialog) { - if (argv[i] && - (!strcmp(argv[i], "--crapplet") || - !strcmp(argv[i], "--capplet"))) - { -# if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2) - int j; - crapplet_p = True; - for (j = i; j < argc; j++) /* remove it from the list */ - argv[j] = argv[j+1]; - argc--; -# else /* !HAVE_CRAPPLET && !HAVE_GTK2 */ - fprintf (stderr, "%s: not compiled with --crapplet support\n", - real_progname); - fprintf (stderr, "%s: %s\n", real_progname, usage); - exit (1); -# endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */ - } - else if (argv[i] && - (!strcmp(argv[i], "--debug") || - !strcmp(argv[i], "-debug") || - !strcmp(argv[i], "-d"))) - { - int j; - s->debug_p = True; - for (j = i; j < argc; j++) /* remove it from the list */ - argv[j] = argv[j+1]; - argc--; - i--; - } - else if (argv[i] && - argc > i+1 && - *argv[i+1] && - (!strcmp(argv[i], "-geometry") || - !strcmp(argv[i], "-geom") || - !strcmp(argv[i], "-geo") || - !strcmp(argv[i], "-g"))) - { - int j; - geom = argv[i+1]; - for (j = i; j < argc; j++) /* remove them from the list */ - argv[j] = argv[j+2]; - argc -= 2; - i -= 2; - } - else if (argv[i] && - argc > i+1 && - *argv[i+1] && - (!strcmp(argv[i], "--configdir"))) - { - int j; - struct stat st; - hack_configuration_path = argv[i+1]; - for (j = i; j < argc; j++) /* remove them from the list */ - argv[j] = argv[j+2]; - argc -= 2; - i -= 2; - - if (0 != stat (hack_configuration_path, &st)) - { - char buf[255]; - sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path); - perror (buf); - exit (1); - } - else if (!S_ISDIR (st.st_mode)) - { - fprintf (stderr, "%s: not a directory: %s\n", - blurb(), hack_configuration_path); - exit (1); - } - } + gtk_widget_set_sensitive (dialog->cmd_label, sensitive_p); + gtk_widget_set_sensitive (dialog->cmd_text, sensitive_p); + gtk_widget_set_sensitive (dialog->manual, sensitive_p); + gtk_widget_set_sensitive (dialog->visual, sensitive_p); + gtk_widget_set_sensitive (dialog->visual_combo, sensitive_p); } +} - if (s->debug_p) - fprintf (stderr, "%s: using config directory \"%s\"\n", - progname, hack_configuration_path); +/* Flush out any changes made in the popup dialog box (where changes + take place only when the OK button is clicked.) + */ +static Bool +flush_popup_changes_and_save (state *s) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (s->dialog); + Bool changed = FALSE; + saver_preferences *p = &s->prefs; + int list_elt = selected_list_element (s); + + GtkEntry *cmd = GTK_ENTRY (dialog->cmd_text); + GtkWidget *vis = GTK_WIDGET (dialog->visual_combo); + GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))); + + const char *visual = gtk_entry_get_text (visent); + const char *command = gtk_entry_get_text (cmd); + + char c; + unsigned long id; + + if (s->saving_p) return FALSE; + s->saving_p = TRUE; + + if (list_elt < 0) + goto DONE; + + if (maybe_reload_init_file (s) != 0) + { + changed = TRUE; + goto DONE; + } - /* Let Gtk open the X connection, then initialize Xt to use that - same connection. Doctor Frankenstein would be proud. + /* Sanity-check and canonicalize whatever the user typed into the combo box. */ -# ifdef HAVE_CRAPPLET - if (crapplet_p) + if (!strcasecmp (visual, "")) visual = ""; + else if (!strcasecmp (visual, "any")) visual = ""; + else if (!strcasecmp (visual, "default")) visual = "Default"; + else if (!strcasecmp (visual, "default-n")) visual = "Default-N"; + else if (!strcasecmp (visual, "default-i")) visual = "Default-I"; + else if (!strcasecmp (visual, "best")) visual = "Best"; + else if (!strcasecmp (visual, "mono")) visual = "Mono"; + else if (!strcasecmp (visual, "monochrome")) visual = "Mono"; + else if (!strcasecmp (visual, "gray")) visual = "Gray"; + else if (!strcasecmp (visual, "grey")) visual = "Gray"; + else if (!strcasecmp (visual, "color")) visual = "Color"; + else if (!strcasecmp (visual, "gl")) visual = "GL"; + else if (!strcasecmp (visual, "staticgray")) visual = "StaticGray"; + else if (!strcasecmp (visual, "staticcolor")) visual = "StaticColor"; + else if (!strcasecmp (visual, "truecolor")) visual = "TRUEColor"; + else if (!strcasecmp (visual, "grayscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "greyscale")) visual = "GrayScale"; + else if (!strcasecmp (visual, "pseudocolor")) visual = "PseudoColor"; + else if (!strcasecmp (visual, "directcolor")) visual = "DirectColor"; + else if (1 == sscanf (visual, " %lu %c", &id, &c)) ; + else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ; + else visual = ""; + + changed = flush_changes (s, list_elt, -1, command, visual); + if (changed) { - GnomeClient *client; - GnomeClientFlags flags = 0; - - int init_results = gnome_capplet_init ("screensaver-properties", - s->short_version, - argc, argv, NULL, 0, NULL); - /* init_results is: - 0 upon successful initialization; - 1 if --init-session-settings was passed on the cmdline; - 2 if --ignore was passed on the cmdline; - -1 on error. - - So the 1 signifies just to init the settings, and quit, basically. - (Meaning launch the xscreensaver daemon.) - */ + demo_write_init_file (s, p); - if (init_results < 0) - { -# if 0 - g_error ("An initialization error occurred while " - "starting xscreensaver-capplet.\n"); -# else /* !0 */ - fprintf (stderr, "%s: gnome_capplet_init failed: %d\n", - real_progname, init_results); - exit (1); -# endif /* !0 */ - } + /* Do this to re-launch the hack if (and only if) the command line + has changed. */ + populate_demo_window (s, selected_list_element (s)); + } - client = gnome_master_client (); + DONE: + s->saving_p = FALSE; + return changed; +} - if (client) - flags = gnome_client_get_flags (client); - if (flags & GNOME_CLIENT_IS_CONNECTED) - { - int token = - gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES", - gnome_client_get_id (client)); - if (token) - { - char *session_args[20]; - int i = 0; - session_args[i++] = real_progname; - session_args[i++] = "--capplet"; - session_args[i++] = "--init-session-settings"; - session_args[i] = 0; - gnome_client_set_priority (client, 20); - gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY); - gnome_client_set_restart_command (client, i, session_args); - } - else - { - gnome_client_set_restart_style (client, GNOME_RESTART_NEVER); - } +/**************************************************************************** - gnome_client_flush (client); - } + XScreenSaverWindow - if (init_results == 1) - { - system ("xscreensaver -nosplash &"); - return 0; - } + ****************************************************************************/ - } + +static void +xscreensaver_window_destroy (GObject *object) +{ + /* Called by WM close box, but not by File / Quit */ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (object); + quit_menu_cb (NULL, win); /* Shouldn't return? */ + G_OBJECT_CLASS (xscreensaver_window_parent_class)->dispose (object); +} + + +/* gtk_window_move() sets the origin of the window's WM decorations, but + GTK's "configure-event" returns the root-relative origin of the window + within the decorations, so the "configure-event" numbers are too large by + the size of the decorations (title bar and border). Without compensating + for this, the window would move down and slightly to the right every time + we saved and restored. GDK provides no way to find those numbers, so we + have to hack it out X11 style... + */ +static void +wm_decoration_origin (GtkWindow *gtkw, int *x, int *y) +{ + Display *dpy = gdk_x11_get_default_xdisplay(); + GdkWindow *gdkw = gtk_widget_get_window (GTK_WIDGET (gtkw)); + Window xw = gdk_x11_window_get_xid (gdkw); + + Window root, parent, *kids; + unsigned int nkids; + + Atom type = None; + int format; + unsigned long nitems, bytesafter; + unsigned char *data; + + static Atom swm_vroot = 0; + XWindowAttributes xgwa; + + if (!dpy || !xw) return; + if (! XQueryTree (dpy, xw, &root, &parent, &kids, &nkids)) + abort (); + if (kids) XFree ((char *) kids); + + if (parent == root) /* No window above us at all */ + return; + + if (! swm_vroot) + swm_vroot = XInternAtom (dpy, "__SWM_VROOT", FALSE); + + /* If parent is a virtual root, there is no intervening WM decoration. */ + if (XGetWindowProperty (dpy, parent, swm_vroot, + 0, 0, FALSE, AnyPropertyType, + &type, &format, &nitems, &bytesafter, + (unsigned char **) &data) + == Success + && type != None) + return; + + /* If we have a parent, it is the WM decoration, so use its origin. */ + if (! XGetWindowAttributes (dpy, parent, &xgwa)) + abort(); + *x = xgwa.x; + *y = xgwa.y; +} + + +static void +save_window_position (state *s, GtkWindow *win, int x, int y, Bool dialog_p) +{ + saver_preferences *p = &s->prefs; + int win_x, win_y, dialog_x, dialog_y; + char dummy; + char *old = p->settings_geom; + char str[100]; + + if (!s->dpy || s->wayland_p) return; + wm_decoration_origin (win, &x, &y); + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_p) + dialog_x = x, dialog_y = y; else -# endif /* HAVE_CRAPPLET */ + win_x = x, win_y = y; + + sprintf (str, "%d,%d %d,%d", win_x, win_y, dialog_x, dialog_y); + + if (old && !strcmp (old, str)) return; /* unchanged */ + + p->settings_geom = strdup (str); + + if (s->debug_p) + fprintf (stderr, "%s: geom: %s => %s\n", blurb(), + (old ? old : "null"), str); + + /* This writes the .xscreensaver file repeatedly as the window is dragged, + which is too much. We could defer it with a timer. But instead let's + just not save it upon resize, and only save the positions once the + file is written due to some other change. + */ + /* demo_write_init_file (s, p); */ + if (old) free (old); +} + + +static void +restore_window_position (state *s, GtkWindow *window, Bool dialog_p) +{ + saver_preferences *p = &s->prefs; + int win_x, win_y, dialog_x, dialog_y, x, y; + char dummy; + char *old = p->settings_geom; + + if (!old || !*old || + 4 != sscanf (old, " %d , %d %d , %d %c", + &win_x, &win_y, &dialog_x, &dialog_y, &dummy)) + win_x = win_y = dialog_x = dialog_y = -1; + + if (dialog_p) + x = dialog_x, y = dialog_y; + else + x = win_x, y = win_y; + + if (x <= 0 || y <= 0) return; + + if (s->debug_p) + fprintf (stderr, "%s: restore origin: %d,%d\n", blurb(), x, y); + gtk_window_move (window, x, y); +} + + +/* When the window is moved, save the new origin in .xscreensaver. */ +static gboolean +xscreensaver_window_resize_cb (GtkWindow *window, GdkEvent *event, + gpointer data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (window); + state *s = &win->state; + save_window_position (s, GTK_WINDOW (win), + event->configure.x, event->configure.y, FALSE); + return FALSE; +} + + +static int +delayed_scroll_kludge (gpointer data) +{ + state *s = (state *) data; + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (s->window); + GtkWidget *list_widget = win->list; + ensure_selected_item_visible (s, list_widget); + return FALSE; /* do not re-execute timer */ +} + + +static void +quit_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + quit_menu_cb (NULL, user_data); +} + + +static GActionEntry app_entries[] = { + { "quit", quit_action_cb, NULL, NULL, NULL }, +/* + { "undo", undo_action_cb, NULL, NULL, NULL }, + { "redo", redo_action_cb, NULL, NULL, NULL }, + { "cut", cut_action_cb, NULL, NULL, NULL }, + { "copy", copy_action_cb, NULL, NULL, NULL }, + { "paste", paste_action_cb, NULL, NULL, NULL }, + { "delete", delete_action_cb, NULL, NULL, NULL }, +*/ +}; + +/* #### I don't know how to make the accelerators show up on the menu items, + and I don't understand the difference between "callbacks" and "actions". + I see examples in other .ui files that do things like: + + <ui> + <menubar name="menubar"> + <menu action="file"> + <menuitem name="activate_menu" action="activate_action"/> + or + <menu id="menubar"> + <submenu> + <attribute name="label">File</attribute> + <section> + <item> + <attribute name="label">Activate</attribute> + <attribute name="action">app.activate</attribute> + + but when I put variants of that in demo.ui, nothing shows up. + + It would be nice to have an "Edit" menu with working text-editing commands + on it, for use with our various text fields. One would think that a GUI + toolkit would provide boilerplate for such things, but nooooo... + */ +const gchar *accels[][2] = { + { "app.quit", "<Ctrl>Q" }, +/* + { "app.undo", "<Ctrl>Z" }, + { "app.redo", "<Ctrl>Y" }, + { "app.cut", "<Ctrl>X" }, + { "app.copy", "<Ctrl>C" }, + { "app.paste", "<Ctrl>V" }, +*/ +}; + + +static void +xscreensaver_window_realize (GtkWidget *self, gpointer user_data) +{ + XScreenSaverWindow *win = XSCREENSAVER_WINDOW (self); + state *s = &win->state; + saver_preferences *p = &s->prefs; + + s->initializing_p = TRUE; + s->short_version = XSCREENSAVER_VERSION; + s->window = GTK_WINDOW (win); + + s->dpy = gdk_x11_get_default_xdisplay(); + + /* Debian 11.4, Gtk 3.24.24, 2022: under Wayland, get_default_xdisplay is + returning uninitialized data! However, gdk_x11_window_get_xid prints a + warning and returns NULL. So let's try that, and as a fallback, also try + and sanity check the contents of the Display structure... + */ + if (! gdk_x11_window_get_xid (gtk_widget_get_window (self))) { - gtk_init (&argc, &argv); + s->dpy = NULL; + s->wayland_p = TRUE; } + if (s->dpy) + { + if (ProtocolVersion (s->dpy) != 11 || + ProtocolRevision (s->dpy) != 0) + { + fprintf (stderr, "%s: uninitialized data in Display: " + "protocol version %d.%d!\n", blurb(), + ProtocolVersion(s->dpy), ProtocolRevision(s->dpy)); + s->dpy = NULL; + s->wayland_p = TRUE; + } + } - /* We must read exactly the same resources as xscreensaver. - That means we must have both the same progclass *and* progname, - at least as far as the resource database is concerned. So, - put "xscreensaver" in argv[0] while initializing Xt. + /* If we don't have a display connection, then we are surely under Wayland + even if the environment variable is not set. */ - argv[0] = "xscreensaver"; - progname = argv[0]; + if (!s->dpy && + !getenv ("WAYLAND_DISPLAY") && + !getenv ("WAYLAND_SOCKET")) + putenv ("WAYLAND_DISPLAY=probably"); + if (getenv ("WAYLAND_DISPLAY") || + getenv ("WAYLAND_SOCKET")) + s->wayland_p = TRUE; - /* Teach Xt to use the Display that Gtk/Gdk have already opened. - */ - XtToolkitInitialize (); - app = XtCreateApplicationContext (); - dpy = GDK_DISPLAY(); - XtAppSetFallbackResources (app, defaults); - XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv); - toplevel_shell = XtAppCreateShell (progname, progclass, - applicationShellWidgetClass, - dpy, 0, 0); - - dpy = XtDisplay (toplevel_shell); - db = XtDatabase (dpy); - XtGetApplicationNameAndClass (dpy, (char **) &progname, &progclass); - XSetErrorHandler (demo_ehandler); - - /* Let's just ignore these. They seem to confuse Irix Gtk... */ - signal (SIGPIPE, SIG_IGN); - - /* After doing Xt-style command-line processing, complain about any - unrecognized command-line arguments. + /* If GTK is running directly under Wayland, try to open an X11 connection + to XWayland anyway, so that get_string_resource and load_init_file work. */ - for (i = 1; i < argc; i++) + if (! s->dpy) { - char *str = argv[i]; - if (str[0] == '-' && str[1] == '-') - str++; - if (!strcmp (str, "-prefs")) - prefs_p = True; - else if (!strcmp (str, "-settings")) - settings_p = True; - else if (crapplet_p) - /* There are lots of random args that we don't care about when we're - started as a crapplet, so just ignore unknown args in that case. */ - ; - else - { - fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, - argv[i]); - fprintf (stderr, "%s: %s\n", real_progname, usage); - exit (1); - } + s->dpy = XOpenDisplay (NULL); + if (s->debug_p) + { + if (s->dpy) + fprintf (stderr, "%s: opened secondary XWayland connection\n", + blurb()); + else + fprintf (stderr, "%s: failed to open XWayland connection\n", + blurb()); + } } - /* Load the init file, which may end up consulting the X resource database - and the site-wide app-defaults file. Note that at this point, it's - important that `progname' be "xscreensaver", rather than whatever - was in argv[0]. + /* Teach Xt to use the Display that Gtk/Gdk have already opened. */ - p->db = db; - s->nscreens = screen_count (dpy); + if (s->dpy) + { + XtAppContext app; + int argc = 0; + char *argv[2]; + const char *oprogname = progname; - init_xscreensaver_atoms (dpy); - hack_environment (s); /* must be before initialize_sort_map() */ + progname = "xscreensaver"; /* For X resources */ + argv[argc++] = (char *) progname; + argv[argc] = 0; - load_init_file (dpy, p); - initialize_sort_map (s); + XtToolkitInitialize (); + app = XtCreateApplicationContext (); + XtAppSetFallbackResources (app, defaults); + XtDisplayInitialize (app, s->dpy, progname, progclass, 0, 0, &argc, argv); - /* Now that Xt has been initialized, and the resources have been read, - we can set our `progname' variable to something more in line with - reality. - */ - progname = real_progname; + if (s->debug_p) + { + XSync (s->dpy, False); + XSynchronize (s->dpy, True); /* Must be after XtDisplayInitialize */ + } + /* Result discarded and leaked */ + XtAppCreateShell (progname, progclass, applicationShellWidgetClass, + s->dpy, 0, 0); + p->db = XtDatabase (s->dpy); + XSetErrorHandler (x_error); -#if 0 - /* Print out all the resources we read. */ - { - XrmName name = { 0 }; - XrmClass class = { 0 }; - int count = 0; - XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper, - (POINTER) &count); - } -#endif + signal (SIGPIPE, SIG_IGN); /* Is this necessary? */ - init_xscreensaver_atoms (dpy); + progname = oprogname; - /* Create the window and all its widgets. - */ - s->base_widget = create_xscreensaver_demo (); - s->popup_widget = create_xscreensaver_settings_dialog (); - s->toplevel_widget = s->base_widget; +# if 0 + /* Print out all the Xrm resources we read. */ + { + XrmName name = { 0 }; + XrmClass class = { 0 }; + int count = 0; + XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, xrm_mapper, + (void *) &count); + } +# endif + } + s->multi_screen_p = multi_screen_p (s->dpy); - /* Set the main window's title. */ + /* Let's see if the server supports DPMS. + */ + s->dpms_supported_p = FALSE; +# ifdef HAVE_DPMS_EXTENSION { - char *base_title = _("Screensaver Preferences"); - char *v = (char *) strdup(strchr(screensaver_id, ' ')); - char *s1, *s2, *s3, *s4; - s1 = (char *) strchr(v, ' '); s1++; - s2 = (char *) strchr(s1, ' '); - s3 = (char *) strchr(v, '('); s3++; - s4 = (char *) strchr(s3, ')'); - *s2 = 0; - *s4 = 0; - - 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); + int op = 0, event = 0, error = 0; + if (s->dpy && XQueryExtension (s->dpy, "DPMS", &op, &event, &error)) + /* Technically this should also check DPMSCapable(), but this is + almost certainly close enough. */ + s->dpms_supported_p = TRUE; + else if (s->debug_p) + fprintf (stderr, "%s: server does not support power management\n", + blurb()); } +# else /* !HAVE_DPMS_EXTENSION */ + if (s->debug_p) + fprintf (stderr, "%s: DPMS not supported at compile time\n", blurb()); +# endif /* !HAVE_DPMS_EXTENSION */ - /* Adjust the (invisible) notebooks on the popup dialog... */ - { - GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (s, "opt_notebook")); - GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button")); - int page = 0; - -# ifdef HAVE_XML - gtk_widget_hide (std); -# else /* !HAVE_XML */ - /* Make the advanced page be the only one available. */ - gtk_widget_set_sensitive (std, False); - std = GTK_WIDGET (name_to_widget (s, "adv_button")); - gtk_widget_hide (std); - std = GTK_WIDGET (name_to_widget (s, "reset_button")); - gtk_widget_hide (std); - page = 1; -# endif /* !HAVE_XML */ - - gtk_notebook_set_page (notebook, page); - gtk_notebook_set_show_tabs (notebook, False); - } +# if defined(__APPLE__) && !defined(__OPTIMIZE__) + s->dpms_supported_p = TRUE; /* macOS X11: debugging kludge */ +# endif - /* Various other widget initializations... - */ - gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event", - GTK_SIGNAL_FUNC (wm_toplevel_close_cb), - (gpointer) s); - gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event", - GTK_SIGNAL_FUNC (wm_popup_close_cb), - (gpointer) s); + if (s->dpy) + init_xscreensaver_atoms (s->dpy); + + hack_environment (s); /* must be before initialize_sort_map() */ + load_init_file (s->dpy, p); + initialize_sort_map (s); + + s->gl_visual = get_best_gl_visual (s); + s->dialog = g_object_new (XSCREENSAVER_DIALOG_TYPE, NULL); + XSCREENSAVER_DIALOG (s->dialog)->main = win; + gtk_window_set_transient_for (GTK_WINDOW (s->dialog), GTK_WINDOW (win)); + + sensitize_menu_items (s, TRUE); populate_hack_list (s); populate_prefs_page (s); - sensitize_demo_widgets (s, False); - fix_text_entry_sizes (s); + sensitize_demo_widgets (s, FALSE); scroll_to_current_hack (s); + if (s->dpy && !s->wayland_p) + fix_preview_visual (s); + if (! s->multi_screen_p) + hide_mode_menu_random_same (s); + + restore_window_position (s, GTK_WINDOW (self), FALSE); - gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")), - "map", GTK_SIGNAL_FUNC(map_popup_window_cb), - (gpointer) s); - -#ifndef HAVE_GTK2 - gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")), - "map", GTK_SIGNAL_FUNC(map_prev_button_cb), - (gpointer) s); - gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")), - "map", GTK_SIGNAL_FUNC(map_next_button_cb), - (gpointer) s); -#endif /* !HAVE_GTK2 */ - - /* Hook up callbacks to the items on the mode menu. */ - gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "mode_menu")), - "changed", GTK_SIGNAL_FUNC (mode_menu_item_cb), - (gpointer) s); - if (s->nscreens <= 1) + g_timeout_add (60 * 1000, check_blanked_timer, s); + + /* Attach the actions and their keybindings. */ + { + int i; + GtkApplication *app = gtk_window_get_application (GTK_WINDOW (win)); + g_action_map_add_action_entries (G_ACTION_MAP (app), + app_entries, countof (app_entries), + win); + for (i = 0; i < countof (accels); i++) + { + const gchar *a[2]; + a[0] = accels[i][1]; + a[1] = 0; + gtk_application_set_accels_for_action (GTK_APPLICATION (app), + accels[i][0], a); + } + } + +# if 0 + /* Load every configurator in turn, to scan them for errors all at once. */ + if (s->debug_p) { - GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu")); - GtkTreeModel *list = gtk_combo_box_get_model (opt); - unsigned int i; - for (i = 0; i < countof(mode_menu_order); i++) + int i; + for (i = 0; i < p->screenhacks_count; i++) { - /* The "random-same" mode menu item does not appear unless - there are multiple screens. - */ - if (mode_menu_order[i] == RANDOM_HACKS_SAME) - { - GtkTreeIter iter; - gtk_tree_model_iter_nth_child (list, &iter, NULL, i); - gtk_list_store_remove (GTK_LIST_STORE (list), &iter); - break; - } + screenhack *hack = p->screenhacks[i]; + conf_data *d = load_configurator (hack->command, s->debug_p); + if (d) free_conf_data (d); } - - /* recompute option-menu size */ - gtk_widget_unrealize (GTK_WIDGET (opt)); - gtk_widget_realize (GTK_WIDGET (opt)); } +# endif - - /* Handle the -prefs command-line argument. */ - if (prefs_p) + /* Grab the screenshot pixmap before mapping the window. */ + if (s->dpy && !s->wayland_p) { - GtkNotebook *notebook = - GTK_NOTEBOOK (name_to_widget (s, "notebook")); - gtk_notebook_set_page (notebook, 1); + GdkWindow *gw = gtk_widget_get_window (win->preview); + Window xw = gdk_x11_window_get_xid (gw); + s->screenshot = screenshot_grab (s->dpy, xw, TRUE, s->debug_p); } -# ifdef HAVE_CRAPPLET - if (crapplet_p) - { - GtkWidget *capplet; - GtkWidget *outer_vbox; + /* Issue any warnings about the running xscreensaver daemon. + Wait a few seconds, in case things are still starting up. */ + g_timeout_add (5 * 1000, the_network_is_not_the_computer, s); - gtk_widget_hide (s->toplevel_widget); + /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds + after we start up. Otherwise, it always appears scrolled to the top. */ + g_timeout_add (500, delayed_scroll_kludge, s); - capplet = capplet_widget_new (); + s->initializing_p = FALSE; +} - /* Make there be a "Close" button instead of "OK" and "Cancel" */ -# ifdef HAVE_CRAPPLET_IMMEDIATE - capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet)); -# endif /* HAVE_CRAPPLET_IMMEDIATE */ - /* In crapplet-mode, take off the menubar. */ - gtk_widget_hide (name_to_widget (s, "menubar")); - /* Reparent our top-level container to be a child of the capplet - window. - */ - outer_vbox = GTK_BIN (s->toplevel_widget)->child; - gtk_widget_ref (outer_vbox); - gtk_container_remove (GTK_CONTAINER (s->toplevel_widget), - outer_vbox); - STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING); - gtk_container_add (GTK_CONTAINER (capplet), outer_vbox); - - /* Find the window above us, and set the title and close handler. */ - { - GtkWidget *window = capplet; - while (window && !GTK_IS_WINDOW (window)) - window = GET_PARENT (window); - if (window) - { - gtk_window_set_title (GTK_WINDOW (window), window_title); - gtk_signal_connect (GTK_OBJECT (window), "delete_event", - GTK_SIGNAL_FUNC (wm_toplevel_close_cb), - (gpointer) s); - } - } +static void +xscreensaver_window_init (XScreenSaverWindow *win) +{ + gtk_widget_init_template (GTK_WIDGET (win)); + g_signal_connect (win, "destroy", + G_CALLBACK (xscreensaver_window_destroy), win); + g_signal_connect (win, "realize", + G_CALLBACK (xscreensaver_window_realize), win); + g_signal_connect (win, "configure-event", + G_CALLBACK (xscreensaver_window_resize_cb),win); + g_signal_connect (win->preview, "configure-event", + G_CALLBACK (preview_resize_cb),win); +} - s->toplevel_widget = capplet; - } -# endif /* HAVE_CRAPPLET */ +static void +xscreensaver_window_class_init (XScreenSaverWindowClass *class) +{ + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), + "/org/jwz/xscreensaver/demo.ui"); + + /* Fill in the widget fields in XScreenSaverWindow with the corresponding + objects created from demo.ui. */ +# undef W +# define W(NAME) \ + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), \ + XScreenSaverWindow, NAME); + ALL_WINDOW_WIDGETS +# undef W +} - /* 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 + XScreenSaverDialog - gtk_widget_show (s->toplevel_widget); - init_icon (GET_WINDOW (GTK_WIDGET (s->toplevel_widget))); /* after `show' */ - fix_preview_visual (s); + ****************************************************************************/ - /* Realize page zero, so that we can diddle the scrollbar when the - user tabs back to it -- otherwise, the current hack isn't scrolled - to the first time they tab back there, when started with "-prefs". - (Though it is if they then tab away, and back again.) +static void +xscreensaver_dialog_destroy (GObject *object) +{ + /* Called by WM close box, but not by File / Quit */ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (object); + XScreenSaverWindow *win = dialog->main; + flush_dialog_changes_and_save (&win->state); + G_OBJECT_CLASS (xscreensaver_dialog_parent_class)->dispose (object); +} - #### Bah! This doesn't work. Gtk eats my ass! Someone who - #### understands this crap, explain to me how to make this work. - */ - gtk_widget_realize (name_to_widget (s, "demos_table")); +static void +xscreensaver_dialog_realize (GtkWidget *self, gpointer user_data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (self); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + restore_window_position (s, GTK_WINDOW (self), TRUE); +} - gtk_timeout_add (60 * 1000, check_blanked_timer, s); +/* When the window is moved, save the new origin in .xscreensaver. */ +static gboolean +xscreensaver_dialog_resize_cb (GtkWindow *window, GdkEvent *event, + gpointer data) +{ + XScreenSaverDialog *dialog = XSCREENSAVER_DIALOG (window); + XScreenSaverWindow *win = dialog->main; + state *s = &win->state; + save_window_position (s, GTK_WINDOW (dialog), + event->configure.x, event->configure.y, TRUE); + return FALSE; +} - /* Handle the --settings command-line argument. */ - if (settings_p) - gtk_timeout_add (500, settings_timer, 0); +/* The WM close box. */ +static gboolean +xscreensaver_dialog_delete_cb (GtkWidget *self, GdkEvent *event, + gpointer user_data) +{ + settings_cancel_cb (GTK_WIDGET (self), user_data); + return TRUE; /* Do not run other handlers */ +} - /* Issue any warnings about the running xscreensaver daemon. */ - if (! s->debug_p) - the_network_is_not_the_computer (s); +static void +xscreensaver_dialog_init (XScreenSaverDialog *win) +{ + gtk_widget_init_template (GTK_WIDGET (win)); + g_signal_connect (win, "destroy", + G_CALLBACK (xscreensaver_dialog_destroy), win); + g_signal_connect (win, "realize", + G_CALLBACK (xscreensaver_dialog_realize), win); + g_signal_connect (win, "configure-event", + G_CALLBACK (xscreensaver_dialog_resize_cb), win); + g_signal_connect (win, "delete-event", + G_CALLBACK (xscreensaver_dialog_delete_cb), win); +} - if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17) - warning_dialog (s->toplevel_widget, - _("Warning:\n\n" - "This version of xscreensaver is VERY OLD!\n" - "Please upgrade!\n" - "\n" - "https://www.jwz.org/xscreensaver/\n" - "\n" - "(If this is the latest version that your distro ships, then\n" - "your distro is doing you a disservice. Build from source.)\n" - ), - D_NONE, 7); - /* Run the Gtk event loop, and not the Xt event loop. This means that - if there were Xt timers or fds registered, they would never get serviced, - and if there were any Xt widgets, they would never have events delivered. - Fortunately, we're using Gtk for all of the UI, and only initialized - Xt so that we could process the command line and use the X resource - manager. - */ - s->initializing_p = False; +static void +xscreensaver_dialog_class_init (XScreenSaverDialogClass *class) +{ + gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), + "/org/jwz/xscreensaver/prefs.ui"); + + /* Fill in the widget fields in XScreenSaverDialog with the corresponding + objects created from prefs.ui. */ +# undef W +# define W(NAME) \ + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), \ + XScreenSaverDialog, NAME); + ALL_DIALOG_WIDGETS +# undef W +} - /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds - after we start up. Otherwise, it always appears scrolled to the top - when in crapplet-mode. */ - gtk_timeout_add (500, delayed_scroll_kludge, s); +/**************************************************************************** + + XScreenSaverApp + + ****************************************************************************/ -#if 1 - /* Load every configurator in turn, to scan them for errors all at once. */ - 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 +static void +xscreensaver_app_init (XScreenSaverApp *app) +{ +} -# ifdef HAVE_CRAPPLET - if (crapplet_p) - capplet_gtk_main (); + +static void +xscreensaver_app_startup (GApplication *app) +{ + G_APPLICATION_CLASS (xscreensaver_app_parent_class)->startup (app); + + /* Without this, the floating point numbers in the XML files are not + parsed properly in locales that use commas instead of periods in + floats: sscanf %f expects "1.0" to be "1,0" and returns 0. + + This must be called later than main() because something beneath + g_application_run() calls setlocale(LC_ALL, "") and would override it. + */ +# ifdef ENABLE_NLS + if (!setlocale (LC_NUMERIC, "C")) + fprintf (stderr, "%s: warning: could not set LC_NUMERIC=C\n", blurb()); +# endif /* ENABLE_NLS */ +} + + +static void +xscreensaver_app_activate (GApplication *app) +{ + XScreenSaverWindow *win = + g_object_new (XSCREENSAVER_WINDOW_TYPE, "application", app, NULL); + win->state.debug_p = XSCREENSAVER_APP (app)->cmdline_debug_p; + gtk_widget_show_all (GTK_WIDGET (win)); + gtk_window_present (GTK_WINDOW (win)); +} + + +static void +xscreensaver_app_open (GApplication *app, + GFile **files, gint n_files, + const gchar *hint) +{ + GList *windows = gtk_application_get_windows (GTK_APPLICATION (app)); + if (windows) + gtk_window_present (GTK_WINDOW (windows->data)); else -# endif /* HAVE_CRAPPLET */ - gtk_main (); + xscreensaver_app_activate (app); +} + - kill_preview_subproc (s, False); - exit (0); +static int +opts_cb (GApplication *app, GVariantDict *opts, gpointer data) +{ + if (g_variant_dict_contains (opts, "version")) { + fprintf (stderr, "%s\n", screensaver_id+4); + return 0; + } else if (g_variant_dict_contains (opts, "debug")) { + XSCREENSAVER_APP (app)->cmdline_debug_p = TRUE; + return -1; + } else { + return -1; + } } + +static void +xscreensaver_app_class_init (XScreenSaverAppClass *class) +{ + G_APPLICATION_CLASS (class)->startup = xscreensaver_app_startup; + G_APPLICATION_CLASS (class)->activate = xscreensaver_app_activate; + G_APPLICATION_CLASS (class)->open = xscreensaver_app_open; +} + +static XScreenSaverApp * +xscreensaver_app_new (void) +{ + XScreenSaverApp *app = g_object_new (XSCREENSAVER_APP_TYPE, + "application-id", + "org.jwz.xscreensaver.settings", + NULL); + + g_application_add_main_option (G_APPLICATION (app), "version", 'v', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + "Print the version number", + NULL); + g_application_add_main_option (G_APPLICATION (app), "debug", 0, + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + "Print diagnostics to stderr", + NULL); + g_signal_connect (app, "handle-local-options", G_CALLBACK (opts_cb), app); + return app; +} + + +int +main (int argc, char *argv[]) +{ + char *s; + progname = argv[0]; + s = strrchr (progname, '/'); + if (s) progname = s+1; + g_log_set_default_handler (g_logger, NULL); + g_log_set_writer_func (g_other_logger, NULL, NULL); + +# ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + textdomain (GETTEXT_PACKAGE); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +# endif /* ENABLE_NLS */ + + return g_application_run (G_APPLICATION (xscreensaver_app_new()), + argc, argv); +} + + + #endif /* HAVE_GTK -- whole file */ |