1247 lines
41 KiB
C
1247 lines
41 KiB
C
/*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* Originally written by: Ray Strode <rstrode@redhat.com>
|
|
*
|
|
* Later contributions by: Matthias Clasen <mclasen@redhat.com>
|
|
* Søren Sandmann <sandmann@redhat.com>
|
|
*/
|
|
|
|
#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"
|
|
|
|
#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", NULL, 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);
|
|
static void screen_saver_on_expose_event (ScreenSaver *screen_saver,
|
|
GdkEventExpose *event);
|
|
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);
|
|
|
|
g_signal_connect_swapped (G_OBJECT (drawing_area), "expose-event",
|
|
G_CALLBACK (screen_saver_on_expose_event),
|
|
screen_saver);
|
|
|
|
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
|
|
screen_saver_on_expose_event (ScreenSaver *screen_saver,
|
|
GdkEventExpose *event)
|
|
{
|
|
GList *tmp;
|
|
cairo_t *context;
|
|
|
|
if (screen_saver->floaters == NULL)
|
|
screen_saver_create_floaters (screen_saver);
|
|
|
|
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);
|
|
|
|
screen_saver->floaters = g_list_sort (screen_saver->floaters,
|
|
(GCompareFunc)compare_floaters);
|
|
|
|
for (tmp = screen_saver->floaters; tmp != NULL; tmp = tmp->next)
|
|
{
|
|
ScreenSaverFloater *floater;
|
|
GdkRectangle rect;
|
|
gint size;
|
|
|
|
floater = (ScreenSaverFloater *) tmp->data;
|
|
|
|
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;
|
|
|
|
if (!screen_saver_floater_do_draw (screen_saver, floater, context))
|
|
{
|
|
gtk_main_quit ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
cairo_destroy (context);
|
|
|
|
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;
|
|
|
|
GtkStateType state;
|
|
|
|
GError *error;
|
|
|
|
error = NULL;
|
|
|
|
gtk_init_with_args (&argc, &argv,
|
|
/* translators: the word "image" here
|
|
* represents a command line argument
|
|
*/
|
|
_("image - floats images around the screen"),
|
|
options, NULL, &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 ();
|
|
|
|
state = (GtkStateType) 0;
|
|
while (state < (GtkStateType) G_N_ELEMENTS (drawing_area->style->bg))
|
|
{
|
|
gtk_widget_modify_bg (drawing_area, state, &drawing_area->style->mid[state]);
|
|
state++;
|
|
}
|
|
|
|
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;
|
|
}
|