/* * Copyright (C) 2005 Ray Strode <rstrode@redhat.com>, * Matthias Clasen <mclasen@redhat.com>, * Søren Sandmann <sandmann@redhat.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. * * Originally written by: Ray Strode <rstrode@redhat.com> * * Later contributions by: Matthias Clasen <mclasen@redhat.com> * Søren Sandmann <sandmann@redhat.com> */ #include "config.h" #include <math.h> #include <stdlib.h> #include <sysexits.h> #include <time.h> #include <glib.h> #include <glib/gi18n.h> #include <gdk/gdk.h> #include <gdk/gdkx.h> #include <gtk/gtk.h> #include "gs-theme-window.h" #if GTK_CHECK_VERSION (3, 0, 0) #define GTK_WIDGET_REALIZED gtk_widget_get_realized #endif #ifndef trunc #define trunc(x) (((x) > 0.0) ? floor((x)) : -floor(-(x))) #endif #ifndef OPTIMAL_FRAME_RATE #define OPTIMAL_FRAME_RATE (25.0) #endif #ifndef STAT_PRINT_FREQUENCY #define STAT_PRINT_FREQUENCY (2000) #endif #ifndef FLOATER_MAX_SIZE #define FLOATER_MAX_SIZE (128.0) #endif #ifndef FLOATER_MIN_SIZE #define FLOATER_MIN_SIZE (16.0) #endif #ifndef FLOATER_DEFAULT_COUNT #define FLOATER_DEFAULT_COUNT (5) #endif #ifndef SMALL_ANGLE #define SMALL_ANGLE (0.025 * G_PI) #endif #ifndef BIG_ANGLE #define BIG_ANGLE (0.125 * G_PI) #endif #ifndef GAMMA #define GAMMA 2.2 #endif static gboolean should_show_paths = FALSE; static gboolean should_do_rotations = FALSE; static gboolean should_print_stats = FALSE; static gint max_floater_count = FLOATER_DEFAULT_COUNT; static gchar *geometry = NULL; static gchar **filenames = NULL; static GOptionEntry options[] = { { "show-paths", 'p', 0, G_OPTION_ARG_NONE, &should_show_paths, N_("Show paths that images follow"), NULL }, { "do-rotations", 'r', 0, G_OPTION_ARG_NONE, &should_do_rotations, N_("Occasionally rotate images as they move"), NULL }, { "print-stats", 's', 0, G_OPTION_ARG_NONE, &should_print_stats, N_("Print out frame rate and other statistics"), NULL }, { "number-of-images", 'n', 0, G_OPTION_ARG_INT, &max_floater_count, N_("The maximum number of images to keep on screen"), N_("MAX_IMAGES") }, { "geometry", 0, 0, G_OPTION_ARG_STRING, &geometry, N_("The initial size and position of window"), N_("WIDTHxHEIGHT+X+Y") }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, N_("The source image to use"), NULL }, {NULL} }; typedef struct _Point Point; typedef struct _Path Path; typedef struct _Rectangle Rectangle; typedef struct _ScreenSaverFloater ScreenSaverFloater; typedef struct _CachedSource CachedSource; typedef struct _ScreenSaver ScreenSaver; struct _Point { gdouble x, y; }; struct _Path { Point start_point; Point start_control_point; Point end_control_point; Point end_point; gdouble x_linear_coefficient, y_linear_coefficient; gdouble x_quadratic_coefficient, y_quadratic_coefficient; gdouble x_cubic_coefficient, y_cubic_coefficient; gdouble duration; }; struct _CachedSource { cairo_pattern_t *pattern; gint width, height; }; struct _Rectangle { Point top_left_point; Point bottom_right_point; }; struct _ScreenSaverFloater { GdkRectangle bounds; Point start_position; Point position; gdouble scale; gdouble opacity; Path *path; gdouble path_start_time; gdouble path_start_scale; gdouble path_end_scale; gdouble angle; gdouble angle_increment; }; struct _ScreenSaver { GtkWidget *drawing_area; Rectangle canvas_rectangle; GHashTable *cached_sources; char *filename; gdouble first_update_time; gdouble last_calculated_stats_time, current_calculated_stats_time; gint update_count, frame_count; gdouble updates_per_second; gdouble frames_per_second; guint state_update_timeout_id; guint stats_update_timeout_id; GList *floaters; gint max_floater_count; guint should_do_rotations: 1; guint should_show_paths : 1; guint draw_ops_pending : 1; }; static Path *path_new (Point *start_point, Point *start_control_point, Point *end_control_point, Point *end_point, gdouble duration); static void path_free (Path *path); static ScreenSaverFloater *screen_saver_floater_new (ScreenSaver *screen_saver, Point *position, gdouble scale); static void screen_saver_floater_free (ScreenSaver *screen_saver, ScreenSaverFloater *floater); static gboolean screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver, ScreenSaverFloater *floater); static gboolean screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble performance_ratio, gdouble *duration); static gboolean screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble performance_ratio, gdouble *duration); static Point screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time); static gdouble screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time); static gdouble screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time); static Path *screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration); static Path *screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration); static Path *screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration); static Path *screen_saver_floater_create_path (ScreenSaver *screen_saver, ScreenSaverFloater *floater); static void screen_saver_floater_update_state (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time); static gboolean screen_saver_floater_do_draw (ScreenSaver *screen_saver, ScreenSaverFloater *floater, cairo_t *context); static CachedSource *cached_source_new (cairo_pattern_t *pattern, gint width, gint height); static void cached_source_free (CachedSource *source); static ScreenSaver *screen_saver_new (GtkDrawingArea *drawing_area, const gchar *filename, gint max_floater_count, gboolean should_do_rotations, gboolean should_show_paths); static void screen_saver_free (ScreenSaver *screen_saver); static gdouble screen_saver_get_timestamp (ScreenSaver *screen_saver); static void screen_saver_get_initial_state (ScreenSaver *screen_saver); static void screen_saver_update_state (ScreenSaver *screen_saver, gdouble time); static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver); static gboolean screen_saver_do_update_stats (ScreenSaver *screen_saver); static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver); static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver); static gdouble screen_saver_get_image_cache_usage (ScreenSaver *screen_saver); static void screen_saver_create_floaters (ScreenSaver *screen_saver); static void screen_saver_destroy_floaters (ScreenSaver *screen_saver); static void screen_saver_on_size_allocate (ScreenSaver *screen_saver, GtkAllocation *allocation); #if GTK_CHECK_VERSION (3, 0, 0) static void screen_saver_on_draw (ScreenSaver *screen_saver, cairo_t *context); #else static void screen_saver_on_expose_event (ScreenSaver *screen_saver, GdkEventExpose *event); #endif static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver); static GdkPixbuf *gamma_correct (const GdkPixbuf *input_pixbuf); static CachedSource* cached_source_new (cairo_pattern_t *pattern, gint width, gint height) { CachedSource *source; source = g_new (CachedSource, 1); source->pattern = cairo_pattern_reference (pattern); source->width = width; source->height = height; return source; } static void cached_source_free (CachedSource *source) { if (source == NULL) return; cairo_pattern_destroy (source->pattern); g_free (source); } static Path * path_new (Point *start_point, Point *start_control_point, Point *end_control_point, Point *end_point, gdouble duration) { Path *path; path = g_new (Path, 1); path->start_point = *start_point; path->start_control_point = *start_control_point; path->end_control_point = *end_control_point; path->end_point = *end_point; path->duration = duration; /* we precompute the coefficients to the cubic bezier curve here * so that we don't have to do it repeatedly later The equation is: * * B(t) = A * t^3 + B * t^2 + C * t + start_point */ path->x_linear_coefficient = 3 * (start_control_point->x - start_point->x); path->x_quadratic_coefficient = 3 * (end_control_point->x - start_control_point->x) - path->x_linear_coefficient; path->x_cubic_coefficient = end_point->x - start_point->x - path->x_linear_coefficient - path->x_quadratic_coefficient; path->y_linear_coefficient = 3 * (start_control_point->y - start_point->y); path->y_quadratic_coefficient = 3 * (end_control_point->y - start_control_point->y) - path->y_linear_coefficient; path->y_cubic_coefficient = end_point->y - start_point->y - path->y_linear_coefficient - path->y_quadratic_coefficient; return path; } static void path_free (Path *path) { g_free (path); } static ScreenSaverFloater* screen_saver_floater_new (ScreenSaver *screen_saver, Point *position, gdouble scale) { ScreenSaverFloater *floater; floater = g_new (ScreenSaverFloater, 1); floater->bounds.width = 0; floater->start_position = *position; floater->position = *position; floater->scale = scale; floater->opacity = pow (scale, 1.0 / GAMMA); floater->path = NULL; floater->path_start_time = 0.0; floater->path_start_scale = 1.0; floater->path_end_scale = 0.0; floater->angle = 0.0; floater->angle_increment = 0.0; return floater; } void screen_saver_floater_free (ScreenSaver *screen_saver, ScreenSaverFloater *floater) { if (floater == NULL) return; path_free (floater->path); g_free (floater); } static gboolean screen_saver_floater_is_off_canvas (ScreenSaver *screen_saver, ScreenSaverFloater *floater) { if ((floater->position.x < screen_saver->canvas_rectangle.top_left_point.x) || (floater->position.x > screen_saver->canvas_rectangle.bottom_right_point.x) || (floater->position.y < screen_saver->canvas_rectangle.top_left_point.y) || (floater->position.y > screen_saver->canvas_rectangle.bottom_right_point.y)) return TRUE; return FALSE; } static gboolean screen_saver_floater_should_come_on_screen (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble performance_ratio, gdouble *duration) { if (!screen_saver_floater_is_off_canvas (screen_saver, floater)) return FALSE; if ((abs (performance_ratio - .5) >= G_MINDOUBLE) && (g_random_double () > .5)) { if (duration) *duration = g_random_double_range (3.0, 7.0); return TRUE; } return FALSE; } static gboolean screen_saver_floater_should_bubble_up (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble performance_ratio, gdouble *duration) { if ((performance_ratio < .5) && (g_random_double () > .5)) { if (duration) *duration = performance_ratio * 30.0; return TRUE; } if ((floater->scale < .3) && (g_random_double () > .6)) { if (duration) *duration = 30.0; return TRUE; } return FALSE; } static Point screen_saver_floater_get_position_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time) { Point point; time = time / floater->path->duration; point.x = floater->path->x_cubic_coefficient * (time * time * time) + floater->path->x_quadratic_coefficient * (time * time) + floater->path->x_linear_coefficient * (time) + floater->path->start_point.x; point.y = floater->path->y_cubic_coefficient * (time * time * time) + floater->path->y_quadratic_coefficient * (time * time) + floater->path->y_linear_coefficient * (time) + floater->path->start_point.y; return point; } static gdouble screen_saver_floater_get_scale_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time) { gdouble completion_ratio, total_scale_growth, new_scale; completion_ratio = time / floater->path->duration; total_scale_growth = (floater->path_end_scale - floater->path_start_scale); new_scale = floater->path_start_scale + total_scale_growth * completion_ratio; return CLAMP (new_scale, 0.0, 1.0); } static gdouble screen_saver_floater_get_angle_from_time (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time) { gdouble completion_ratio; gdouble total_rotation; completion_ratio = time / floater->path->duration; total_rotation = floater->angle_increment * floater->path->duration; return floater->angle + total_rotation * completion_ratio; } static Path * screen_saver_floater_create_path_to_on_screen (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration) { Point start_position, end_position, start_control_point, end_control_point; start_position = floater->position; end_position.x = g_random_double_range (.25, .75) * (screen_saver->canvas_rectangle.top_left_point.x + screen_saver->canvas_rectangle.bottom_right_point.x); end_position.y = g_random_double_range (.25, .75) * (screen_saver->canvas_rectangle.top_left_point.y + screen_saver->canvas_rectangle.bottom_right_point.y); start_control_point.x = start_position.x + .9 * (end_position.x - start_position.x); start_control_point.y = start_position.y + .9 * (end_position.y - start_position.y); end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x); end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y); return path_new (&start_position, &start_control_point, &end_control_point, &end_position, duration); } static Path * screen_saver_floater_create_path_to_bubble_up (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration) { Point start_position, end_position, start_control_point, end_control_point; start_position = floater->position; end_position.x = start_position.x; end_position.y = screen_saver->canvas_rectangle.top_left_point.y - FLOATER_MAX_SIZE; start_control_point.x = .5 * start_position.x; start_control_point.y = .5 * start_position.y; end_control_point.x = 1.5 * end_position.x; end_control_point.y = .5 * end_position.y; return path_new (&start_position, &start_control_point, &end_control_point, &end_position, duration); } static Path * screen_saver_floater_create_path_to_random_point (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble duration) { Point start_position, end_position, start_control_point, end_control_point; start_position = floater->position; end_position.x = start_position.x + (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE); end_position.y = start_position.y + (g_random_double_range (-.5, .5) * 4 * FLOATER_MAX_SIZE); start_control_point.x = start_position.x + .95 * (end_position.x - start_position.x); start_control_point.y = start_position.y + .95 * (end_position.y - start_position.y); end_control_point.x = start_position.x + 1.0 * (end_position.x - start_position.x); end_control_point.y = start_position.y + 1.0 * (end_position.y - start_position.y); return path_new (&start_position, &start_control_point, &end_control_point, &end_position, duration); } static Path * screen_saver_floater_create_path (ScreenSaver *screen_saver, ScreenSaverFloater *floater) { gdouble performance_ratio; gdouble duration; performance_ratio = screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE; if (abs (performance_ratio) <= G_MINDOUBLE) performance_ratio = 1.0; if (screen_saver_floater_should_bubble_up (screen_saver, floater, performance_ratio, &duration)) return screen_saver_floater_create_path_to_bubble_up (screen_saver, floater, duration); if (screen_saver_floater_should_come_on_screen (screen_saver, floater, performance_ratio, &duration)) return screen_saver_floater_create_path_to_on_screen (screen_saver, floater, duration); return screen_saver_floater_create_path_to_random_point (screen_saver, floater, g_random_double_range (3.0, 7.0)); } static void screen_saver_floater_update_state (ScreenSaver *screen_saver, ScreenSaverFloater *floater, gdouble time) { gdouble performance_ratio; performance_ratio = screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE; if (floater->path == NULL) { floater->path = screen_saver_floater_create_path (screen_saver, floater); floater->path_start_time = time; floater->path_start_scale = floater->scale; if (g_random_double () > .5) floater->path_end_scale = g_random_double_range (0.10, performance_ratio); /* poor man's distribution */ if (screen_saver->should_do_rotations && (g_random_double () < .75 * performance_ratio)) { gint r; r = g_random_int_range (0, 100); if (r < 80) floater->angle_increment = 0.0; else if (r < 95) floater->angle_increment = g_random_double_range (-SMALL_ANGLE, SMALL_ANGLE); else floater->angle_increment = g_random_double_range (-BIG_ANGLE, BIG_ANGLE); } } if (time < (floater->path_start_time + floater->path->duration)) { gdouble path_time; path_time = time - floater->path_start_time; floater->position = screen_saver_floater_get_position_from_time (screen_saver, floater, path_time); floater->scale = screen_saver_floater_get_scale_from_time (screen_saver, floater, path_time); floater->angle = screen_saver_floater_get_angle_from_time (screen_saver, floater, path_time); floater->opacity = pow (floater->scale, 1.0 / GAMMA); } else { path_free (floater->path); floater->path = NULL; floater->path_start_time = 0.0; } } static GdkPixbuf * gamma_correct (const GdkPixbuf *input_pixbuf) { gint x, y, width, height, rowstride; GdkPixbuf *output_pixbuf; guchar *pixels; output_pixbuf = gdk_pixbuf_copy (input_pixbuf); pixels = gdk_pixbuf_get_pixels (output_pixbuf); width = gdk_pixbuf_get_width (output_pixbuf); height = gdk_pixbuf_get_height (output_pixbuf); rowstride = gdk_pixbuf_get_rowstride (output_pixbuf); for (y = 0; y < height; y++) for (x = 0; x < width; x++) { guchar *alpha_channel; guchar opacity; alpha_channel = pixels + y * (rowstride / 4) + x + 3; opacity = (guchar) (255 * pow ((*alpha_channel / 255.0), 1.0 / GAMMA)); *alpha_channel = opacity; } return output_pixbuf; } static gboolean screen_saver_floater_do_draw (ScreenSaver *screen_saver, ScreenSaverFloater *floater, cairo_t *context) { gint size; CachedSource *source; size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); source = g_hash_table_lookup (screen_saver->cached_sources, GINT_TO_POINTER (size)); if (source == NULL) { GdkPixbuf *pixbuf; GError *error; pixbuf = NULL; error = NULL; pixbuf = gdk_pixbuf_new_from_file_at_size (screen_saver->filename, size, -1, &error); if (pixbuf == NULL) { g_assert (error != NULL); g_printerr ("%s", _(error->message)); g_error_free (error); return FALSE; } if (gdk_pixbuf_get_has_alpha (pixbuf)) gamma_correct (pixbuf); gdk_cairo_set_source_pixbuf (context, pixbuf, 0.0, 0.0); source = cached_source_new (cairo_get_source (context), gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf)); g_object_unref (pixbuf); g_hash_table_insert (screen_saver->cached_sources, GINT_TO_POINTER (size), source); } cairo_save (context); if (screen_saver->should_do_rotations && (abs (floater->angle) > G_MINDOUBLE)) { floater->bounds.width = G_SQRT2 * source->width + 2; floater->bounds.height = G_SQRT2 * source->height + 2; floater->bounds.x = (int) (floater->position.x - .5 * G_SQRT2 * source->width) - 1; floater->bounds.y = (int) (floater->position.y - .5 * G_SQRT2 * source->height) - 1; cairo_translate (context, trunc (floater->position.x), trunc (floater->position.y)); cairo_rotate (context, floater->angle); cairo_translate (context, -trunc (floater->position.x), -trunc (floater->position.y)); } else { floater->bounds.width = source->width + 2; floater->bounds.height = source->height + 2; floater->bounds.x = (int) (floater->position.x - .5 * source->width) - 1; floater->bounds.y = (int) (floater->position.y - .5 * source->height) - 1; } cairo_translate (context, trunc (floater->position.x - .5 * source->width), trunc (floater->position.y - .5 * source->height)); cairo_set_source (context, source->pattern); cairo_rectangle (context, trunc (.5 * (source->width - floater->bounds.width)), trunc (.5 * (source->height - floater->bounds.height)), floater->bounds.width, floater->bounds.height); cairo_clip (context); cairo_paint_with_alpha (context, floater->opacity); cairo_restore (context); if (screen_saver->should_show_paths && (floater->path != NULL)) { gdouble dash_pattern[] = { 5.0 }; gint size; size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->path_start_scale), FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); cairo_save (context); cairo_set_source_rgba (context, 1.0, 1.0, 1.0, .2 * floater->opacity); cairo_move_to (context, floater->path->start_point.x, floater->path->start_point.y); cairo_curve_to (context, floater->path->start_control_point.x, floater->path->start_control_point.y, floater->path->end_control_point.x, floater->path->end_control_point.y, floater->path->end_point.x, floater->path->end_point.y); cairo_set_line_cap (context, CAIRO_LINE_CAP_ROUND); cairo_stroke (context); cairo_set_source_rgba (context, 1.0, 0.0, 0.0, .5 * floater->opacity); cairo_rectangle (context, floater->path->start_point.x - 3, floater->path->start_point.y - 3, 6, 6); cairo_fill (context); cairo_set_source_rgba (context, 0.0, 0.5, 0.0, .5 * floater->opacity); cairo_arc (context, floater->path->start_control_point.x, floater->path->start_control_point.y, 3, 0.0, 2.0 * G_PI); cairo_stroke (context); cairo_set_source_rgba (context, 0.5, 0.0, 0.5, .5 * floater->opacity); cairo_arc (context, floater->path->end_control_point.x, floater->path->end_control_point.y, 3, 0.0, 2.0 * G_PI); cairo_stroke (context); cairo_set_source_rgba (context, 0.0, 0.0, 1.0, .5 * floater->opacity); cairo_rectangle (context, floater->path->end_point.x - 3, floater->path->end_point.y - 3, 6, 6); cairo_fill (context); cairo_set_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0); cairo_set_source_rgba (context, .5, .5, .5, .2 * floater->scale); cairo_move_to (context, floater->path->start_point.x, floater->path->start_point.y); cairo_line_to (context, floater->path->start_control_point.x, floater->path->start_control_point.y); cairo_stroke (context); cairo_move_to (context, floater->path->end_point.x, floater->path->end_point.y); cairo_line_to (context, floater->path->end_control_point.x, floater->path->end_control_point.y); cairo_stroke (context); cairo_restore (context); } return TRUE; } static ScreenSaver * screen_saver_new (GtkDrawingArea *drawing_area, const gchar *filename, gint max_floater_count, gboolean should_do_rotations, gboolean should_show_paths) { ScreenSaver *screen_saver; screen_saver = g_new (ScreenSaver, 1); screen_saver->filename = g_strdup (filename); screen_saver->drawing_area = GTK_WIDGET (drawing_area); screen_saver->cached_sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) cached_source_free); g_signal_connect_swapped (G_OBJECT (drawing_area), "size-allocate", G_CALLBACK (screen_saver_on_size_allocate), screen_saver); #if GTK_CHECK_VERSION (3, 0, 0) g_signal_connect_swapped (G_OBJECT (drawing_area), "draw", G_CALLBACK (screen_saver_on_draw), screen_saver); #else g_signal_connect_swapped (G_OBJECT (drawing_area), "expose-event", G_CALLBACK (screen_saver_on_expose_event), screen_saver); #endif screen_saver->first_update_time = 0.0; screen_saver->current_calculated_stats_time = 0.0; screen_saver->last_calculated_stats_time = 0.0; screen_saver->update_count = 0; screen_saver->frame_count = 0; screen_saver->updates_per_second = 0.0; screen_saver->frames_per_second = 0.0; screen_saver->floaters = NULL; screen_saver->max_floater_count = max_floater_count; screen_saver->should_show_paths = should_show_paths; screen_saver->should_do_rotations = should_do_rotations; screen_saver_get_initial_state (screen_saver); screen_saver->state_update_timeout_id = g_timeout_add (1000 / (2.0 * OPTIMAL_FRAME_RATE), (GSourceFunc) screen_saver_do_update_state, screen_saver); screen_saver->stats_update_timeout_id = g_timeout_add (1000, (GSourceFunc) screen_saver_do_update_stats, screen_saver); return screen_saver; } static void screen_saver_free (ScreenSaver *screen_saver) { if (screen_saver == NULL) return; g_free (screen_saver->filename); g_hash_table_destroy (screen_saver->cached_sources); if (screen_saver->state_update_timeout_id != 0) g_source_remove (screen_saver->state_update_timeout_id); if (screen_saver->stats_update_timeout_id != 0) g_source_remove (screen_saver->stats_update_timeout_id); screen_saver_destroy_floaters (screen_saver); g_free (screen_saver); } static gdouble screen_saver_get_timestamp (ScreenSaver *screen_saver) { const gdouble microseconds_per_second = (gdouble ) G_USEC_PER_SEC; gdouble timestamp; GTimeVal now = { 0L, /* zero-filled */ }; g_get_current_time (&now); timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / microseconds_per_second; return timestamp; } static void screen_saver_create_floaters (ScreenSaver *screen_saver) { gint i; for (i = 0; i < screen_saver->max_floater_count; i++) { ScreenSaverFloater *floater; Point position; gdouble scale; position.x = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.x, screen_saver->canvas_rectangle.bottom_right_point.x); position.y = g_random_double_range (screen_saver->canvas_rectangle.top_left_point.y, screen_saver->canvas_rectangle.bottom_right_point.y); scale = g_random_double (); floater = screen_saver_floater_new (screen_saver, &position, scale); screen_saver->floaters = g_list_prepend (screen_saver->floaters, floater); } } static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver) { return screen_saver->updates_per_second; } static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver) { return screen_saver->frames_per_second; } static gdouble screen_saver_get_image_cache_usage (ScreenSaver *screen_saver) { static const gdouble cache_capacity = (FLOATER_MAX_SIZE - FLOATER_MIN_SIZE + 1); return g_hash_table_size (screen_saver->cached_sources) / cache_capacity; } static void screen_saver_destroy_floaters (ScreenSaver *screen_saver) { if (screen_saver->floaters == NULL) return; g_list_foreach (screen_saver->floaters, (GFunc) screen_saver_floater_free, NULL); g_list_free (screen_saver->floaters); screen_saver->floaters = NULL; } static void screen_saver_on_size_allocate (ScreenSaver *screen_saver, GtkAllocation *allocation) { Rectangle canvas_rectangle; canvas_rectangle.top_left_point.x = allocation->x - .1 * allocation->width; canvas_rectangle.top_left_point.y = allocation->y - .1 * allocation->height; canvas_rectangle.bottom_right_point.x = allocation->x + (1.1 * allocation->width); canvas_rectangle.bottom_right_point.y = allocation->y + (1.1 * allocation->height); screen_saver->canvas_rectangle = canvas_rectangle; } static gint compare_floaters (ScreenSaverFloater *a, ScreenSaverFloater *b) { if (a->scale > b->scale) return 1; else if (abs (a->scale - b->scale) <= G_MINDOUBLE) return 0; else return -1; } static void #if GTK_CHECK_VERSION (3, 0, 0) screen_saver_on_draw (ScreenSaver *screen_saver, cairo_t *context) #else screen_saver_on_expose_event (ScreenSaver *screen_saver, GdkEventExpose *event) #endif { GList *tmp; #if !GTK_CHECK_VERSION (3, 0, 0) cairo_t *context; #endif if (screen_saver->floaters == NULL) screen_saver_create_floaters (screen_saver); #if !GTK_CHECK_VERSION (3, 0, 0) context = gdk_cairo_create (screen_saver->drawing_area->window); cairo_rectangle (context, (double) event->area.x, (double) event->area.y, (double) event->area.width, (double) event->area.height); cairo_clip (context); #endif screen_saver->floaters = g_list_sort (screen_saver->floaters, (GCompareFunc)compare_floaters); for (tmp = screen_saver->floaters; tmp != NULL; tmp = tmp->next) { ScreenSaverFloater *floater; #if !GTK_CHECK_VERSION (3, 0, 0) GdkRectangle rect; gint size; #endif floater = (ScreenSaverFloater *) tmp->data; #if !GTK_CHECK_VERSION (3, 0, 0) size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); rect.x = (int) (floater->position.x - .5 * G_SQRT2 * size); rect.y = (int) (floater->position.y - .5 * G_SQRT2 * size); rect.width = G_SQRT2 * size; rect.height = G_SQRT2 * size; if (!gdk_region_rect_in (event->region, &rect)) continue; #endif if (!screen_saver_floater_do_draw (screen_saver, floater, context)) { gtk_main_quit (); break; } } #if !GTK_CHECK_VERSION (3, 0, 0) cairo_destroy (context); #endif screen_saver->draw_ops_pending = TRUE; screen_saver->frame_count++; } static void screen_saver_update_state (ScreenSaver *screen_saver, gdouble time) { GList *tmp; tmp = screen_saver->floaters; while (tmp != NULL) { ScreenSaverFloater *floater; floater = (ScreenSaverFloater *) tmp->data; screen_saver_floater_update_state (screen_saver, floater, time); if (GTK_WIDGET_REALIZED (screen_saver->drawing_area) && (floater->bounds.width > 0) && (floater->bounds.height > 0)) { gint size; size = CLAMP ((int) (FLOATER_MAX_SIZE * floater->scale), FLOATER_MIN_SIZE, FLOATER_MAX_SIZE); gtk_widget_queue_draw_area (screen_saver->drawing_area, floater->bounds.x, floater->bounds.y, floater->bounds.width, floater->bounds.height); /* the edges could concievably be spread across two * pixels so we add +2 to invalidated region */ if (screen_saver->should_do_rotations) gtk_widget_queue_draw_area (screen_saver->drawing_area, (int) (floater->position.x - .5 * G_SQRT2 * size), (int) (floater->position.y - .5 * G_SQRT2 * size), G_SQRT2 * size + 2, G_SQRT2 * size + 2); else gtk_widget_queue_draw_area (screen_saver->drawing_area, (int) (floater->position.x - .5 * size), (int) (floater->position.y - .5 * size), size + 2, size + 2); if (screen_saver->should_show_paths) gtk_widget_queue_draw (screen_saver->drawing_area); } tmp = tmp->next; } } static void screen_saver_get_initial_state (ScreenSaver *screen_saver) { screen_saver->first_update_time = screen_saver_get_timestamp (screen_saver); screen_saver_update_state (screen_saver, 0.0); } static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver) { gdouble current_update_time; /* flush pending requests to the X server and block for * replies before proceeding to help prevent the X server from * getting overrun with requests */ if (screen_saver->draw_ops_pending) { gdk_flush (); screen_saver->draw_ops_pending = FALSE; } current_update_time = screen_saver_get_timestamp (screen_saver); screen_saver_update_state (screen_saver, current_update_time - screen_saver->first_update_time); screen_saver->update_count++; return TRUE; } static gboolean screen_saver_do_update_stats (ScreenSaver *screen_saver) { gdouble last_calculated_stats_time, seconds_since_last_stats_update; last_calculated_stats_time = screen_saver->current_calculated_stats_time; screen_saver->current_calculated_stats_time = screen_saver_get_timestamp (screen_saver); screen_saver->last_calculated_stats_time = last_calculated_stats_time; if (abs (last_calculated_stats_time) <= G_MINDOUBLE) return TRUE; seconds_since_last_stats_update = screen_saver->current_calculated_stats_time - last_calculated_stats_time; screen_saver->updates_per_second = screen_saver->update_count / seconds_since_last_stats_update; screen_saver->frames_per_second = screen_saver->frame_count / seconds_since_last_stats_update; screen_saver->update_count = 0; screen_saver->frame_count = 0; return TRUE; } static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver) { g_print ("updates per second: %.2f, frames per second: %.2f, " "image cache %.0f%% full\n", screen_saver_get_updates_per_second (screen_saver), screen_saver_get_frames_per_second (screen_saver), screen_saver_get_image_cache_usage (screen_saver) * 100.0); return TRUE; } int main (int argc, char *argv[]) { ScreenSaver *screen_saver; GtkWidget *window; GtkWidget *drawing_area; #if GTK_CHECK_VERSION (3, 0, 0) GdkRGBA bg; GdkRGBA fg; #else GtkStyle *style; GtkStateType state; #endif GError *error; error = NULL; bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); gtk_init_with_args (&argc, &argv, /* translators: the word "image" here * represents a command line argument */ _("image - floats images around the screen"), options, GETTEXT_PACKAGE, &error); if (error != NULL) { g_printerr (_("%s. See --help for usage information.\n"), _(error->message)); g_error_free (error); return EX_SOFTWARE; } if ((filenames == NULL) || (filenames[0] == NULL) || (filenames[1] != NULL)) { g_printerr (_("You must specify one image. See --help for usage " "information.\n")); return EX_USAGE; } window = gs_theme_window_new (); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_main_quit), NULL); drawing_area = gtk_drawing_area_new (); #if GTK_CHECK_VERSION (3, 0, 0) bg.red = 0; bg.green = 0; bg.blue = 0; bg.alpha = 1.0; fg.red = 0.8; fg.green = 0.8; fg.blue = 0.8; fg.alpha = 1.0; gtk_widget_override_background_color (drawing_area, 0, &bg); gtk_widget_override_color (drawing_area, 0, &fg); #else style = drawing_area->style; state = (GtkStateType) 0; while (state < (GtkStateType) G_N_ELEMENTS (style->bg)) { gtk_widget_modify_bg (drawing_area, state, &style->mid[state]); state++; } #endif gtk_widget_show (drawing_area); gtk_container_add (GTK_CONTAINER (window), drawing_area); screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area), filenames[0], max_floater_count, should_do_rotations, should_show_paths); g_strfreev (filenames); if (should_print_stats) g_timeout_add (STAT_PRINT_FREQUENCY, (GSourceFunc) do_print_screen_saver_stats, screen_saver); if ((geometry == NULL) || !gtk_window_parse_geometry (GTK_WINDOW (window), geometry)) gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); gtk_widget_show (window); gtk_main (); screen_saver_free (screen_saver); return EX_OK; }