#include #include #include #include #include #include #ifndef UPDATES_PER_SECOND #define UPDATES_PER_SECOND 150 #endif #ifndef OPTIMAL_FRAME_RATE #define OPTIMAL_FRAME_RATE 20 #endif static const guchar fedora_logo_svg[] = "" "" " " " " " " " " " " " " " " " " " " " " " " " " " " " "; typedef struct _Point Point; typedef struct _Path Path; static Path *path_new (Point *start_point, Point *start_control_point, Point *end_control_point, Point *end_point, double duration); static void path_free (Path *path); typedef struct _Rectangle Rectangle; typedef struct _Floater Floater; static Floater *floater_new (Rectangle *canvas_rectangle, const char *filename, Point *position, double scale); static void floater_free (Floater *floater); static gboolean floater_is_off_screen (Floater *floater); static gboolean floater_should_bubble_up (Floater *floater, double performance_ratio, double *duration); static gboolean floater_should_come_on_screen (Floater *floater, double performance_ratio, double *duration); static Point floater_find_position_on_path (Floater *floater, Path *path, double time); static Path *floater_create_path_to_on_screen (Floater *floater, double duration); static Path *floater_create_path_to_bubble_up (Floater *floater, double duration); static Path *floater_create_path_to_random_point (Floater *floater, double duration); static Path *floater_create_path (Floater *floater); static void floater_update_state (Floater *floater, double time); static void floater_do_draw (Floater *floater, cairo_t *context); typedef struct _CachedSource CachedSource; static CachedSource *cached_source_new (cairo_pattern_t *pattern, int width, int height); static void cached_source_free (CachedSource *source); typedef struct _ScreenSaver ScreenSaver; static ScreenSaver *screen_saver_new (GtkDrawingArea *drawing_area, const char *filename); 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 gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver); static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver); static void screen_saver_create_floaters (ScreenSaver *screen_saver, Rectangle *canvas_rectangle); 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 ScreenSaver *screen_saver; struct _Point { double x, y; }; struct _Path { Point start_point; Point start_control_point; Point end_control_point; Point end_point; double duration; }; struct _CachedSource { cairo_pattern_t *pattern; int width, height; }; static CachedSource* cached_source_new (cairo_pattern_t *pattern, int width, int 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); } static Path * path_new (Point *start_point, Point *start_control_point, Point *end_control_point, Point *end_point, double 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; return path; } static void path_free (Path *path) { g_free (path); } struct _Rectangle { Point top_left_point; Point bottom_right_point; }; #ifndef FLOATER_MAX_SIZE #define FLOATER_MAX_SIZE (192) #endif #ifndef FLOATER_MIN_SIZE #define FLOATER_MIN_SIZE (8) #endif #ifndef FLOATER_COUNT #define FLOATER_COUNT (10) #endif struct _Floater { char *filename; Rectangle canvas_rectangle; GdkRectangle bounds; GHashTable *cached_sources; Point start_position; Point position; double scale; double opacity; Path *path; double path_start_time; double path_start_scale; guint path_should_grow : 1; guint path_should_shrink : 1; }; static Floater* floater_new (Rectangle *canvas_rectangle, const char *filename, Point *position, double scale) { Floater *floater; floater = g_new (Floater, 1); floater->canvas_rectangle = *canvas_rectangle; floater->bounds.width = 0; floater->filename = g_strdup (filename); floater->cached_sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) cached_source_free); floater->start_position = *position; floater->position = *position; floater->scale = scale; floater->opacity = scale; floater->path = NULL; floater->path_start_time = 0.0; floater->path_start_scale = 1.0; floater->path_should_grow = FALSE; floater->path_should_shrink = FALSE; return floater; } void floater_free (Floater *floater) { if (floater == NULL) return; g_free (floater->filename); g_hash_table_destroy (floater->cached_sources); path_free (floater->path); g_free (floater); } static gboolean floater_is_off_screen (Floater *floater) { if ((floater->position.x < floater->canvas_rectangle.top_left_point.x) || (floater->position.x > floater->canvas_rectangle.bottom_right_point.x) || (floater->position.y < floater->canvas_rectangle.top_left_point.y) || (floater->position.y > floater->canvas_rectangle.bottom_right_point.y)) return TRUE; return FALSE; } static gboolean floater_should_come_on_screen (Floater *floater, double performance_ratio, double *duration) { if (!floater_is_off_screen (floater)) return FALSE; if ((performance_ratio - .5) >= G_MINDOUBLE) { if (duration) *duration = g_random_double_range (3.0, 7.0); return TRUE; } return FALSE; } static gboolean floater_should_bubble_up (Floater *floater, double performance_ratio, double *duration) { if ((performance_ratio < .5) && (g_random_double() > .5)) { if (duration) *duration = performance_ratio * 30.0; return TRUE; } if ((floater->scale < .30) && floater->position.y > (.75 * (floater->canvas_rectangle.bottom_right_point.y - floater->canvas_rectangle.top_left_point.y))) { if (duration) *duration = 30.0; return TRUE; } return FALSE; } static Point floater_find_position_on_path (Floater *floater, Path *path, double time) { Point point; double ax, ay, bx, by, cx, cy; time = time / path->duration; cx = 3 * (path->start_control_point.x - path->start_point.x); bx = 3 * (path->end_control_point.x - path->start_control_point.x) - cx; ax = path->end_point.x - path->start_point.x - cx - bx; cy = 3 * (path->start_control_point.y - path->start_point.y); by = 3 * (path->end_control_point.y - path->start_control_point.y) - cy; ay = path->end_point.y - path->start_point.y - cy - by; point.x = ax * (time * time * time) + bx * (time * time) + cx * time + path->start_point.x; point.y = ay * (time * time * time) + by * (time * time) + cy * time + path->start_point.y; return point; } static Path * floater_create_path_to_on_screen (Floater *floater, double 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) * (floater->canvas_rectangle.top_left_point.x + floater->canvas_rectangle.bottom_right_point.x); end_position.y = g_random_double_range (.25, .75) * (floater->canvas_rectangle.top_left_point.y + floater->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 * floater_create_path_to_bubble_up (Floater *floater, double 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 = floater->canvas_rectangle.top_left_point.y - floater->bounds.height; 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 * floater_create_path_to_random_point (Floater *floater, double 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 + .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 * floater_create_path (Floater *floater) { double performance_ratio; double duration; performance_ratio = screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE; if (performance_ratio <= G_MINDOUBLE) performance_ratio = 1.0; if (floater_should_bubble_up (floater, performance_ratio, &duration)) return floater_create_path_to_bubble_up (floater, duration); if (floater_should_come_on_screen (floater, performance_ratio, &duration)) return floater_create_path_to_on_screen (floater, duration); return floater_create_path_to_random_point (floater, g_random_double_range (3.0, 7.0)); } static void floater_update_state (Floater *floater, double time) { if (floater->path == NULL) { floater->path = floater_create_path (floater); floater->path_start_time = time; floater->path_should_grow = FALSE; floater->path_should_shrink = FALSE; floater->path_start_scale = floater->scale; if (g_random_double () > (.5 * screen_saver_get_frames_per_second (screen_saver) / OPTIMAL_FRAME_RATE)) { if (floater->scale < .5) floater->path_should_grow = TRUE; else floater->path_should_shrink = TRUE; if (g_random_double () > .9) { floater->path_should_shrink = !floater->path_should_shrink; floater->path_should_grow = !floater->path_should_grow; } } } if (time < (floater->path_start_time + floater->path->duration)) { Point *new_position; double path_time, end_scale; path_time = time - floater->path_start_time; floater->position = floater_find_position_on_path (floater, floater->path, path_time); if (floater->path_should_grow) { end_scale = (1.0 - floater->path_start_scale); floater->scale = floater->path_start_scale; floater->scale += 2 * ((path_time / floater->path->duration) * end_scale); floater->scale = MIN (floater->scale, 1.0); } else if (floater->path_should_shrink) { floater->scale = floater->path_start_scale; floater->scale -= 2 * ((path_time / floater->path->duration)); floater->scale = MAX (floater->scale, .25); } if (g_random_double () > .95) { floater->path_should_grow = FALSE; floater->path_should_shrink = FALSE; } floater->opacity = floater->scale; } else { path_free (floater->path); floater->path = NULL; floater->path_start_time = -1.0; } } static void floater_do_draw (Floater *floater, cairo_t *context) { int size; CachedSource *source; size = MAX ((int) (FLOATER_MAX_SIZE * floater->scale), FLOATER_MIN_SIZE); source = g_hash_table_lookup (floater->cached_sources, GINT_TO_POINTER (size)); if (source == NULL) { GdkPixbuf *pixbuf; GError *error; error = NULL; if (floater->filename != NULL) pixbuf = gdk_pixbuf_new_from_file_at_size (floater->filename, size, -1, &error); else { GdkPixbufLoader *loader; GError *error = NULL; loader = gdk_pixbuf_loader_new (); gdk_pixbuf_loader_set_size (loader, size, size); gdk_pixbuf_loader_write (loader, fedora_logo_svg, sizeof (fedora_logo_svg), &error); if (error == NULL) { gdk_pixbuf_loader_close (loader, &error); pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)); } if (error != NULL) { g_printerr ("could not load inline image at size %d: %s\n", size, error->message); g_error_free (error); } g_object_unref (loader); } if (pixbuf == NULL) { g_assert (error != NULL); g_printerr ("could not load pixbuf at size %d: %s\n", size, error->message); g_error_free (error); return; } 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 (floater->cached_sources, GINT_TO_POINTER (size), source); } floater->bounds.width = source->width; floater->bounds.height = source->height; floater->bounds.x = (int) floater->position.x; floater->bounds.y = (int) floater->position.y; cairo_save (context); cairo_translate (context, floater->position.x, floater->position.y); cairo_set_source (context, source->pattern); cairo_rectangle (context, 0.0, 0.0, source->width, source->height); cairo_clip (context); cairo_paint_with_alpha (context, floater->opacity); cairo_restore (context); #ifdef FLOATER_SHOW_PATH if (floater->path != NULL) { gdouble dash_pattern[] = { 5.0 }; cairo_save (context); cairo_set_source_rgb (context, 1.0, 1.0, 1.0); 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_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0); cairo_set_line_cap (context, CAIRO_LINE_CAP_BUTT); cairo_stroke (context); cairo_set_source_rgb (context, 1.0, 0.0, 0.0); cairo_rectangle (context, floater->path->start_point.x - 2.5, floater->path->start_point.y - 2.5, 5, 5); cairo_fill (context); cairo_set_dash (context, NULL, 0, 0); cairo_set_source_rgb (context, 0.0, 1.0, 0.0); cairo_arc (context, floater->path->start_control_point.x, floater->path->start_control_point.y, 2.5, 0.0, 2.0 * G_PI); cairo_stroke (context); cairo_set_source_rgb (context, 1.0, 0.0, 1.0); cairo_arc (context, floater->path->end_control_point.x, floater->path->end_control_point.y, 2.5, 0.0, 2.0 * G_PI); cairo_stroke (context); cairo_set_dash (context, dash_pattern, G_N_ELEMENTS (dash_pattern), 0); cairo_set_source_rgb (context, 0.0, 0.0, 1.0); cairo_rectangle (context, floater->path->end_point.x - 2.5, floater->path->end_point.y - 2.5, 5, 5); cairo_fill (context); cairo_restore (context); } #endif } struct _ScreenSaver { GtkWidget *drawing_area; gchar *filename; double first_update_time, current_update_time, last_update_time, first_frame_time, current_frame_time, last_frame_time; guint timeout_id; GList *floaters; }; static ScreenSaver * screen_saver_new (GtkDrawingArea *drawing_area, const char *filename) { ScreenSaver *screen_saver; screen_saver = g_new (ScreenSaver, 1); screen_saver->filename = g_strdup (filename); screen_saver->drawing_area = GTK_WIDGET (drawing_area); 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 = -1.0; screen_saver->current_update_time = -1.0; screen_saver->last_update_time = -1.0; screen_saver->first_frame_time = -1.0; screen_saver->current_frame_time = -1.0; screen_saver->last_frame_time = -1.0; screen_saver->floaters = NULL; screen_saver_get_initial_state (screen_saver); #if 0 screen_saver->timeout_id = g_timeout_add (1000 / UPDATES_PER_SECOND, (GSourceFunc) screen_saver_do_update_state, screen_saver); #endif screen_saver->timeout_id = g_idle_add ((GSourceFunc) screen_saver_do_update_state, screen_saver); return screen_saver; } static void screen_saver_free (ScreenSaver *screen_saver) { if (screen_saver == NULL) return; if (screen_saver->timeout_id != 0) g_source_remove (screen_saver->timeout_id); screen_saver_destroy_floaters (screen_saver); g_free (screen_saver->filename); 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, Rectangle *canvas_rectangle) { int i; if (screen_saver->floaters != NULL) screen_saver_destroy_floaters (screen_saver); for (i = 0; i < FLOATER_COUNT; i++) { Floater *floater; Point position; double scale; position.x = g_random_double () * (canvas_rectangle->bottom_right_point.x - canvas_rectangle->top_left_point.x); position.y = g_random_double () * (canvas_rectangle->bottom_right_point.y - canvas_rectangle->top_left_point.y); scale = g_random_double (); floater = floater_new (canvas_rectangle, screen_saver->filename, &position, scale); screen_saver->floaters = g_list_prepend (screen_saver->floaters, floater); } } static gdouble screen_saver_get_updates_per_second (ScreenSaver *screen_saver) { gdouble time_since_last_update; time_since_last_update = screen_saver->current_update_time - screen_saver->last_update_time; if (time_since_last_update > G_MINDOUBLE) return 1.0 / time_since_last_update; return 0.0; } static gdouble screen_saver_get_frames_per_second (ScreenSaver *screen_saver) { gdouble time_since_last_frame; time_since_last_frame = screen_saver->current_frame_time - screen_saver->last_frame_time; if (time_since_last_frame > G_MINDOUBLE) return 1.0 / time_since_last_frame; return 0.0; } static void screen_saver_destroy_floaters (ScreenSaver *screen_saver) { if (screen_saver->floaters == NULL) return; g_list_foreach (screen_saver->floaters, (GFunc) 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_create_floaters (screen_saver, &canvas_rectangle); } static void screen_saver_on_expose_event (ScreenSaver *screen_saver, GdkEventExpose *event) { GList *tmp; cairo_t *context; screen_saver->last_frame_time = screen_saver->current_frame_time; if (screen_saver->first_frame_time < 0) { screen_saver->first_frame_time = screen_saver_get_timestamp (screen_saver); screen_saver->current_frame_time = screen_saver->first_frame_time; } else screen_saver->current_frame_time = screen_saver_get_timestamp (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); tmp = screen_saver->floaters; while (tmp != NULL) { Floater *floater; floater = (Floater *) tmp->data; floater_do_draw (floater, context); tmp = tmp->next; } cairo_destroy (context); } static void screen_saver_update_state (ScreenSaver *screen_saver, gdouble time) { GList *tmp; tmp = screen_saver->floaters; while (tmp != NULL) { Floater *floater; floater = (Floater *) tmp->data; floater_update_state (floater, time); if (GTK_WIDGET_REALIZED (screen_saver->drawing_area) && (floater->bounds.width > 0) && (floater->bounds.height > 0)) { int size; size = MAX ((int) (FLOATER_MAX_SIZE * floater->scale), FLOATER_MIN_SIZE); gtk_widget_queue_draw_area (screen_saver->drawing_area, floater->bounds.x - 1, floater->bounds.y - 1, floater->bounds.width + 2, floater->bounds.height + 2); gtk_widget_queue_draw_area (screen_saver->drawing_area, (int) floater->position.x - 1, (int) floater->position.y - 1, size + 2, size + 2); #if FLOATER_MAX_SIZE gtk_widget_queue_draw (screen_saver->drawing_area); #endif } 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->current_update_time = screen_saver->first_update_time; screen_saver->last_update_time = screen_saver->current_update_time; screen_saver_update_state (screen_saver, 0.0); } static gboolean screen_saver_do_update_state (ScreenSaver *screen_saver) { screen_saver->last_update_time = screen_saver->current_update_time; screen_saver->current_update_time = screen_saver_get_timestamp (screen_saver); screen_saver_update_state (screen_saver, screen_saver->current_update_time - screen_saver->first_update_time); return TRUE; } static gboolean do_print_screen_saver_stats (ScreenSaver *screen_saver) { g_print ("updates per second: %f, frames per second: %f\n", screen_saver_get_updates_per_second (screen_saver), screen_saver_get_frames_per_second (screen_saver)); return TRUE; } static void screen_saver_window_realize (GtkWidget *widget) { GdkWindow *window; GdkWindowAttr attributes = { 0 }; gint attributes_mask; Window remote_xwindow; if (GTK_WIDGET_REALIZED (widget)) return; window = NULL; if (g_getenv ("XSCREENSAVER_WINDOW") != NULL) { remote_xwindow = (Window) strtoul (g_getenv ("XSCREENSAVER_WINDOW"), NULL, 0); if ((remote_xwindow != 0) && (remote_xwindow < G_MAXULONG)) { window = gdk_window_foreign_new (remote_xwindow); if (window != NULL) gdk_window_set_events (window, GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK | GDK_SUBSTRUCTURE_MASK); } } if (window == NULL) return; widget->window = window; gdk_window_set_user_data (window, widget); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *drawing_area; gint x, y, width, height; GtkStateType state; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (gtk_main_quit), NULL); /* FIXME: the right way to do this is to subclass GtkWindow * into ScreenSaverWindow and override the default realize * handler */ screen_saver_window_realize (window); drawing_area = gtk_drawing_area_new (); state = 0; while (state < (GtkStateType) G_N_ELEMENTS (drawing_area->style->bg)) { gtk_widget_modify_bg (drawing_area, state, &drawing_area->style->dark[state]); state++; } gtk_widget_show (drawing_area); gtk_container_add (GTK_CONTAINER (window), drawing_area); if (argc >= 2) screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area), argv[1]); else screen_saver = screen_saver_new (GTK_DRAWING_AREA (drawing_area), NULL); g_timeout_add (2000, (GSourceFunc) do_print_screen_saver_stats, screen_saver); /* FIXME: The next 4 calls are racy and will cause flicker. * See the above FIXME. */ gdk_window_get_geometry (GDK_DRAWABLE (window->window), &x, &y, &width, &height, NULL); gtk_window_set_default_size (GTK_WINDOW (window), width, height); gtk_widget_show (window); gdk_window_move_resize (window->window, x, y, width, height); gtk_main (); return EX_OK; }