using Gtk; using Gdk; using Gnome; using GtkSharp; using System; using GLib; using System.Runtime.InteropServices; using FSpot; namespace FSpot { public class XScreenSaverSlide : Gtk.Window { public const string ScreenSaverEnviroment = "XSCREENSAVER_WINDOW"; public XScreenSaverSlide () : base (String.Empty) { } protected override void OnRealized () { string env = Environment.GetEnvironmentVariable (ScreenSaverEnviroment); if (env != null) { try { env = env.ToLower (); if (env.StartsWith ("0x")) env = env.Substring (2); uint xid = UInt32.Parse (env, System.Globalization.NumberStyles.HexNumber); GdkWindow = Gdk.Window.ForeignNew (xid); Style.Attach (GdkWindow); GdkWindow.Events = EventMask.ExposureMask | EventMask.StructureMask | EventMask.EnterNotifyMask | EventMask.LeaveNotifyMask | EventMask.FocusChangeMask; Style.SetBackground (GdkWindow, Gtk.StateType.Normal); GdkWindow.SetDecorations ((Gdk.WMDecoration) 0); GdkWindow.UserData = this.Handle; SetFlag (WidgetFlags.Realized); SizeRequest (); Gdk.Rectangle geom; int depth; GdkWindow.GetGeometry (out geom.X, out geom.Y, out geom.Width, out geom.Height, out depth); SizeAllocate (new Gdk.Rectangle (geom.X, geom.Y, geom.Width, geom.Height)); Resize (geom.Width, geom.Height); return; } catch (System.Exception e) { System.Console.WriteLine (e); } } else { System.Console.WriteLine ("{0} not set, falling back to window", ScreenSaverEnviroment); } SetSizeRequest (640, 480); base.OnRealized (); } } public class FullSlide : Gtk.Window { private SlideView slideview; private Gdk.Pixbuf screenshot; private Delay hide; private Gdk.Cursor busy; private Gdk.Cursor none; public FullSlide (Gtk.Window parent, IBrowsableItem [] items) : base ("Slideshow") { screenshot = PixbufUtils.LoadFromScreen (parent.GdkWindow); this.Destroyed += HandleDestroyed; this.TransientFor = parent; this.ButtonPressEvent += HandleSlideViewButtonPressEvent; this.KeyPressEvent += HandleSlideViewKeyPressEvent; this.AddEvents ((int) (EventMask.ButtonPressMask | EventMask.KeyPressMask | EventMask.PointerMotionMask)); slideview = new SlideView (screenshot, items); this.Add (slideview); this.Decorated = false; this.Fullscreen(); this.Realize (); busy = new Gdk.Cursor (Gdk.CursorType.Watch); this.GdkWindow.Cursor = busy; none = Empty (); hide = new Delay (2000, new GLib.IdleHandler (HideCursor)); } public Gdk.Cursor Empty () { Gdk.Cursor cempty = null; try { Gdk.Pixbuf empty = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, true, 8, 1, 1); empty.Fill (0x00000000); cempty = new Gdk.Cursor (GdkWindow.Display, empty, 0, 0); } catch (System.Exception e){ System.Console.WriteLine (e.ToString ()); return null; } return cempty; } public void Play () { Gdk.GCValues values = new Gdk.GCValues (); values.SubwindowMode = SubwindowMode.IncludeInferiors; Gdk.GC fillgc = new Gdk.GC (this.GdkWindow, values, Gdk.GCValuesMask.Subwindow); slideview.Show (); this.GdkWindow.SetBackPixmap (null, false); this.Show (); screenshot.RenderToDrawable (this.GdkWindow, fillgc, 0, 0, 0, 0, -1, -1, RgbDither.Normal, 0, 0); slideview.Play (); hide.Start (); } [GLib.ConnectBefore] private void HandleSlideViewKeyPressEvent (object sender, KeyPressEventArgs args) { this.Destroy (); args.RetVal = true; } protected override bool OnMotionNotifyEvent (Gdk.EventMotion args) { base.OnMotionNotifyEvent (args); this.GdkWindow.Cursor = busy; hide.Start (); return true; } private bool HideCursor () { this.GdkWindow.Cursor = none; return false; } private void HandleDestroyed (object sender, System.EventArgs args) { hide.Stop (); } private void HandleSlideViewButtonPressEvent (object sender, ButtonPressEventArgs args) { this.Destroy (); args.RetVal = true; } } public class SlideView : Gtk.Image { IBrowsableItem [] photos; Pixbuf last; Pixbuf next; Pixbuf [] tweens = new Pixbuf [10]; int current_tween; uint tween_idle; uint resize_idle; int current_idx = 0; int next_idx = 0; uint flip_timer = 0; uint transition_timer = 0; uint fail_count = 0; bool animate = true; uint animate_max = 200; bool black = false; uint flip_interval = 2000; uint transition_interval = 75; public bool Running { get { return flip_timer != 0 || transition_timer != 0; } } public bool Animate { get { return animate; } set { animate = value; } } public void Play () { if (photos.Length < 1) return; StopTweenIdle (); if (current_idx >= 0) { Pixbuf frame = GetScaled (photos[current_idx]); this.Pixbuf = frame; frame.Dispose (); } if (PreloadNextImage (current_idx + 1)) StartFlipTimer (); } public void Pause () { StopTranstionTimer (); StopFlipTimer (); } public void Stop () { StopTweenIdle (); StopTranstionTimer (); StopFlipTimer (); } public void Forward () { if (PreloadNextImage (current_idx + 1)) ShowNext (); } public void Back () { if (PreloadNextImage (current_idx - 1)) ShowNext (); } private void ShowNext () { StopTweenIdle (); if (current_idx != next_idx && next != null) this.Pixbuf = next; current_idx = next_idx; black = false; transition_interval = 75; flip_interval = 2000; } private bool PreloadNextImage (int idx) { try { if (idx < photos.Length && idx >= 0) { if (next != null) next.Dispose (); next = GetScaled (photos [idx]); if (next == null) next = GetScaled (PixbufUtils.ShallowCopy (PixbufUtils.ErrorPixbuf)); next_idx = idx; StartTweenIdle (); return true; } else { if (next != null) next.Dispose (); next = GetScaled (photos [0]); if (next == null) next = GetScaled (PixbufUtils.ShallowCopy (PixbufUtils.ErrorPixbuf)); next_idx = 0; StartTweenIdle (); return false; } } catch (GLib.GException e) { System.Console.WriteLine (e); idx = (idx + 1) % photos.Length; return PreloadNextImage (idx); } } private Pixbuf CrossFade (Pixbuf current, Pixbuf prev, Pixbuf next, double percent) { Rectangle area = new Rectangle (0, 0, Allocation.Width, Allocation.Height); BlockProcessor proc = new BlockProcessor (area, 256); Rectangle subarea; while (proc.Step (out subarea)) { if (IsRealized) GdkWindow.ProcessUpdates (false); prev.CopyArea (subarea.X, subarea.Y, subarea.Width, subarea.Height, current, subarea.X, subarea.Y); next.Composite (current, subarea.X, subarea.Y, subarea.Width, subarea.Height, 0, 0, 1, 1, Gdk.InterpType.Nearest, (int) System.Math.Round (255 * percent)); } return current; } private Pixbuf BlackFade (Pixbuf current, Pixbuf prev, Pixbuf next, double percent) { int width = Allocation.Width; int height = Allocation.Height; current.Fill (0); if (percent < 0.5) prev.Composite (current, 0,0, width, height, 0, 0, 1, 1, Gdk.InterpType.Nearest, (int)System.Math.Round (255 * (1 - percent * 2))); else next.Composite (current, 0,0, width, height, 0, 0, 1, 1, Gdk.InterpType.Nearest, (int)System.Math.Round (255 * (percent * 2 - 1))); return current; } private Pixbuf Blend (Pixbuf current, Pixbuf prev, Pixbuf next, double percent) { if (black) { return BlackFade (current, prev, next, percent); } else { return CrossFade (current, prev, next, percent); } } private Pixbuf GetScaled (Pixbuf orig) { Gdk.Rectangle pos; int width = Allocation.Width; int height = Allocation.Height; double scale = PixbufUtils.Fit (orig, width, height, false, out pos.Width, out pos.Height); pos.X = (width - pos.Width) / 2; pos.Y = (height - pos.Height) / 2; Pixbuf scaled = new Pixbuf (Colorspace.Rgb, false, 8, width, height); scaled.Fill (0x000000); Rectangle rect = new Rectangle (pos.X, pos.Y, 256, 256); Rectangle subarea; while (rect.Top < pos.Bottom) { while (rect.X < pos.Right) { if (IsRealized) GdkWindow.ProcessUpdates (false); rect.Intersect (pos, out subarea); orig.Composite (scaled, subarea.X, subarea.Y, subarea.Width, subarea.Height, pos.X, pos.Y, scale, scale, Gdk.InterpType.Bilinear, 255); rect.X += rect.Width; } rect.X = pos.X; rect.Y += rect.Height; } orig.Dispose (); return scaled; } private Pixbuf GetScaled (IBrowsableItem photo) { Pixbuf orig; try { orig = FSpot.PhotoLoader.LoadAtMaxSize (photo, Allocation.Width, Allocation.Height); } catch { orig = null; } if (orig == null) return null; Pixbuf result = GetScaled (orig); if (orig != result) orig.Dispose (); return result; } private bool HandleFlipTimer () { StopTweenIdle (); StartTransitionTimer (); flip_timer = 0; return false; } private bool HandleTransitionTimer () { System.DateTime start_time = System.DateTime.Now; transition_timer = 0; if (current_tween-- > 0) { StartTransitionTimer (); this.Pixbuf = tweens[current_tween]; GdkWindow.ProcessUpdates (false); System.TimeSpan span = System.DateTime.Now - start_time; if (Animate) { if (span.TotalMilliseconds > animate_max) { fail_count++; if (fail_count > 3) { Animate = false; System.Console.WriteLine ("Disabling slide animation due to 3 consecutive excessive frame intervals {0}ms", span.TotalMilliseconds); current_tween = 0; } } else { fail_count = 0; } } } else { ShowNext (); PreloadNextImage (current_idx + 1); StartFlipTimer (); } return false; } private bool HandleTweenIdle () { using (Pixbuf prev = this.Pixbuf) { if (!Animate) { ClearTweens (); return false; } if (photos.Length < 2) { // Only one photo. Nothing to do ClearTweens (); return false; } if (current_tween >= tweens.Length) { tween_idle = 0; return false; } if (current_tween < tweens.Length && tweens[current_tween] == null) { tweens[current_tween] = new Pixbuf (Colorspace.Rgb, false, 8, Allocation.Width, Allocation.Height); } double blend_val; #if USE_EXP double blend_t = (-10 * current_tween) / ((double)tweens.Length - 1); blend_val = 1.0 - (.01 / (.01 + (.99 * Math.Exp(blend_t)))); #else double [] blends = new double [] { .99, .97, .9, .8, .7, .6, .5, .4, .3, .15}; blend_val = blends [current_tween]; #endif tweens[current_tween] = Blend (tweens[current_tween], prev, next, blend_val); current_tween++; return true; } } private void StartTweenIdle () { if (tween_idle == 0) { current_tween = 0; tween_idle = GLib.Idle.Add (new GLib.IdleHandler (HandleTweenIdle)); } } private void StopTweenIdle () { if (tween_idle != 0) { GLib.Source.Remove (tween_idle); } tween_idle = 0; } private void StartTransitionTimer () { if (transition_timer == 0) transition_timer = GLib.Timeout.Add (transition_interval, new TimeoutHandler (HandleTransitionTimer)); } private void StopTranstionTimer () { if (transition_timer != 0) GLib.Source.Remove (transition_timer); transition_timer = 0; } private void StartFlipTimer () { if (flip_timer == 0) flip_timer = GLib.Timeout.Add (flip_interval, new TimeoutHandler (HandleFlipTimer)); } private void StopFlipTimer () { if (flip_timer != 0) GLib.Source.Remove (flip_timer); flip_timer = 0; } private void HandleSizeAllocate (object sender, SizeAllocatedArgs args) { Pixbuf current = this.Pixbuf; if (current == null) return; // // The size has changed so we need to reload the images. // if (current.Width != Allocation.Width || current.Height != Allocation.Height) { bool playing = (flip_timer != 0 || transition_timer != 0); if (current_idx < 0) { using (Gdk.Pixbuf old = this.Pixbuf) { this.Pixbuf = GetScaled (old); current.Dispose (); } } else { using (Pixbuf frame = GetScaled (photos[current_idx])) { this.Pixbuf = frame; current.Dispose (); } } Stop (); ClearTweens (); if (playing && current_idx != next_idx) Play (); } } private void ClearTweens () { for (int i = 0; i < tweens.Length; i++) { if (tweens[i] != null) tweens[i].Dispose (); tweens[i] = null; } } private void HandleDestroyed (object sender, EventArgs args) { ClearTweens (); Stop (); } public SlideView (Pixbuf background, IBrowsableItem [] photos) : base () { this.photos = photos; if (background != null) { this.Pixbuf = background; background.Dispose (); current_idx = -1; black = true; flip_interval = 1500; } SizeAllocated += new SizeAllocatedHandler (HandleSizeAllocate); Destroyed += new EventHandler (HandleDestroyed); } } }