[gnome-boxes] Update screenshot handling



commit a5539fd063d581422795d615b4cca8cc650a54e2
Author: Alexander Larsson <alexl redhat com>
Date:   Wed Jun 20 10:48:03 2012 +0200

    Update screenshot handling
    
    The current screenshot handling is pretty bad, doing sync libvirt
    calls on the mainloop and constantly doing (sometimes sync) i/o to
    disk to access the screenshot data. This is a restructuring of
    the screenshot handling with the following features:
    
    * No sync libvirt calls
    * Read the screenshots from libvirt directly into a pixbuf
    * Save the screenshot caches more rarely
    * Only load the screenshot caches on start
    * Always get a screenshot of the last frame when closing the
      connection
    * Only read the boxes-grid.png file once
    * scale down screenshots using gdk-pixbuf (much better quality downscaling)
    * Work around bug in libvirt screenshot handling after disconnect
    
    https://bugzilla.gnome.org/show_bug.cgi?id=678455

 src/app.vala             |    1 -
 src/libvirt-machine.vala |   29 ++++-------
 src/machine.vala         |  121 +++++++++++++++++++++++++++++++++-------------
 src/remote-machine.vala  |   29 +----------
 4 files changed, 101 insertions(+), 79 deletions(-)
---
diff --git a/src/app.vala b/src/app.vala
index 4eaddc9..732cfcd 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -493,7 +493,6 @@ private class Boxes.App: Boxes.UI {
                 var machine = current_item as Machine;
 
                 machine.disconnect_display ();
-                machine.update_screenshot.begin ();
             }
             fullscreen = false;
             view.visible = true;
diff --git a/src/libvirt-machine.vala b/src/libvirt-machine.vala
index 7599d6e..69256d3 100644
--- a/src/libvirt-machine.vala
+++ b/src/libvirt-machine.vala
@@ -13,15 +13,6 @@ private class Boxes.LibvirtMachine: Boxes.Machine {
         set { source.set_boolean ("source", "save-on-quit", value); }
     }
 
-    public override void disconnect_display () {
-        if (display == null)
-            return;
-
-        App.app.display_page.remove_display ();
-        display.disconnect_it ();
-        display = null;
-    }
-
     private ulong started_id;
     public override void connect_display () {
         if (display != null)
@@ -139,6 +130,7 @@ private class Boxes.LibvirtMachine: Boxes.Machine {
         title = domain_config.get_title () ?? name;
         domain.updated.connect (update_domain_config);
 
+        load_screenshot ();
         set_screenshot_enable (true);
         set_stats_enable (true);
     }
@@ -340,7 +332,7 @@ private class Boxes.LibvirtMachine: Boxes.Machine {
         return domain.get_uuid ();
     }
 
-    public override async bool take_screenshot () throws GLib.Error {
+    public override async Gdk.Pixbuf? take_screenshot () throws GLib.Error {
         var state = DomainState.NONE;
         try {
             state = (yield domain.get_info_async (null)).state;
@@ -349,23 +341,24 @@ private class Boxes.LibvirtMachine: Boxes.Machine {
         }
 
         if (state != DomainState.RUNNING && state != DomainState.PAUSED)
-            return true;
+            return null;
 
         var stream = connection.get_stream (0);
-        var file_name = get_screenshot_filename ();
-        var file = File.new_for_path (file_name);
-        var output_stream = yield file.replace_async (null, false, FileCreateFlags.REPLACE_DESTINATION);
-        var input_stream = stream.get_input_stream ();
-        domain.screenshot (stream, 0, 0);
+        yield run_in_thread (()=> {
+            domain.screenshot (stream, 0, 0);
+        });
 
+        var loader = new Gdk.PixbufLoader ();
+        var input_stream = stream.get_input_stream ();
         var buffer = new uint8[65535];
         ssize_t length = 0;
         do {
             length = yield input_stream.read_async (buffer);
-            yield output_stream_write (output_stream, buffer[0:length]);
+            loader.write (buffer[0:length]);
         } while (length > 0);
+        loader.close ();
 
-        return true;
+        return loader.get_pixbuf ();
     }
 
     public override void delete (bool by_user = true) {
diff --git a/src/machine.vala b/src/machine.vala
index cb1d39d..6ba9921 100644
--- a/src/machine.vala
+++ b/src/machine.vala
@@ -19,6 +19,8 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
     private uint screenshot_id;
     public static const int SCREENSHOT_WIDTH = 180;
     public static const int SCREENSHOT_HEIGHT = 134;
+    private static Cairo.Surface grid_surface;
+    private bool updating_screenshot;
 
     public enum MachineState {
         UNKNOWN,
@@ -94,6 +96,10 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
         }
     }
 
+    static construct {
+        grid_surface = new Cairo.ImageSurface.from_png (get_pixmap ("boxes-grid.png"));
+    }
+
     public Machine (Boxes.CollectionSource source, string name) {
         this.name = name;
         this.source = source;
@@ -107,13 +113,22 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
             else
                 set_screenshot_enable (true);
         });
+
+    }
+
+    public void load_screenshot () {
+        try {
+            var screenshot = new Gdk.Pixbuf.from_file (get_screenshot_filename ());
+            set_screenshot (screenshot, false);
+        } catch (GLib.Error error) {
+        }
     }
 
     public void set_screenshot_enable (bool enable) {
         if (enable) {
             if (screenshot_id != 0)
                 return;
-            update_screenshot.begin ();
+            update_screenshot.begin (false, true);
             var interval = App.app.settings.get_int ("screenshot-interval");
             screenshot_id = Timeout.add_seconds (interval, () => {
                 update_screenshot.begin ();
@@ -127,12 +142,12 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
         }
     }
 
-    public virtual string get_screenshot_filename (string ext = "ppm") {
-        return get_user_pkgcache (get_screenshot_prefix () + "-screenshot." + ext);
+    public string get_screenshot_filename () {
+        return get_user_pkgcache (get_screenshot_prefix () + "-screenshot.png");
     }
 
-    public virtual async bool take_screenshot () throws GLib.Error {
-        return false;
+    public async virtual Gdk.Pixbuf? take_screenshot () throws GLib.Error {
+        return null;
     }
 
     public abstract List<Pair<string, Widget>> get_properties (Boxes.PropertiesPage page);
@@ -140,51 +155,91 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
     public abstract string get_screenshot_prefix ();
 
     public abstract void connect_display ();
-    public abstract void disconnect_display ();
+
+    public virtual void disconnect_display () {
+        if (display == null)
+            return;
+
+        try {
+            var pixbuf = display.get_pixbuf (0);
+            if (pixbuf != null)
+                set_screenshot (pixbuf, true);
+        } catch (GLib.Error error) {
+            warning (error.message);
+        }
+
+        App.app.display_page.remove_display ();
+        display.disconnect_it ();
+        display = null;
+    }
 
     public bool is_running () {
         return state == MachineState.RUNNING;
     }
 
-    public async void update_screenshot (int width = SCREENSHOT_WIDTH, int height = SCREENSHOT_HEIGHT) {
+    public void set_screenshot (Gdk.Pixbuf? large_screenshot, bool save) {
+        if (large_screenshot != null) {
+            var pw = large_screenshot.get_width ();
+            var ph = large_screenshot.get_height ();
+            var s = double.min ((double)SCREENSHOT_WIDTH / pw, (double)SCREENSHOT_HEIGHT / ph);
+            int w = (int) (pw * s);
+            int h = (int) (ph * s);
+
+            var small_screenshot = new Gdk.Pixbuf (Gdk.Colorspace.RGB, large_screenshot.has_alpha, 8, w, h);
+            large_screenshot.scale (small_screenshot, 0, 0, w, h, 0, 0, s, s, Gdk.InterpType.HYPER);
+
+            pixbuf = draw_vm (small_screenshot, SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT);
+            machine_actor.set_screenshot (large_screenshot); // high resolution
+
+            if (save) {
+                try {
+                    pixbuf.save (get_screenshot_filename (), "png");
+                } catch (GLib.Error error) {
+                }
+            }
+        } else if (pixbuf == null) {
+            pixbuf = draw_fallback_vm (SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT);
+            machine_actor.set_screenshot (pixbuf);
+        }
+    }
+
+    int screenshot_counter;
+    public async void update_screenshot (bool force_save = false, bool first_check = false) {
+        if (updating_screenshot)
+            return;
+
+        updating_screenshot = true;
+
+        Gdk.Pixbuf? large_screenshot = null;
         try {
-            yield take_screenshot ();
-            pixbuf = new Gdk.Pixbuf.from_file (get_screenshot_filename ());
-            machine_actor.set_screenshot (pixbuf); // high resolution
-            pixbuf = draw_vm (pixbuf, width, height);
+            large_screenshot = yield take_screenshot ();
+            // There is some kind of bug in libvirt, so the first time we
+            // take a screenshot after displaying the box we get the old
+            // screenshot from before connecting to the box
+            if (first_check)
+                large_screenshot = yield take_screenshot ();
         } catch (GLib.Error error) {
-            if (!(error is FileError.NOENT))
-                warning ("%s: %s".printf (name, error.message));
         }
+        // Save the screenshot first time and every 60 sec
+        set_screenshot (large_screenshot, force_save || screenshot_counter++ % 12 == 0);
 
-        if (pixbuf == null) {
-            pixbuf = draw_fallback_vm (width, height);
-            machine_actor.set_screenshot (pixbuf);
-        }
+        updating_screenshot = false;
     }
 
     private Gdk.Pixbuf draw_vm (Gdk.Pixbuf pixbuf, int width, int height) {
         var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
         var context = new Cairo.Context (surface);
 
-        var pw = (double)pixbuf.get_width ();
-        var ph = (double)pixbuf.get_height ();
-        var sw = width / pw;
-        var sh = height / ph;
-        var x = 0.0;
-        var y = 0.0;
-
-        if (pw > ph) {
-            y = (height - (ph * sw)) / 2;
-            sh = sw;
-        }
+        var pw = pixbuf.get_width ();
+        var ph = pixbuf.get_height ();
+        var x = (width - pw) / 2;
+        var y = (height - ph) / 2;
 
-        context.rectangle (x, y, width - x * 2, height - y * 2);
+        context.rectangle (x, y, pw, ph);
         context.clip ();
 
-        context.scale (sw, sh);
-        Gdk.cairo_set_source_pixbuf (context, pixbuf, x / sw, y / sh);
-        context.get_source ().set_filter (Cairo.Filter.BEST); // FIXME: cairo scaling is crap
+        Gdk.cairo_set_source_pixbuf (context, pixbuf, 0, 0);
+        context.set_operator (Cairo.Operator.SOURCE);
         context.paint ();
 
         if (!is_running ()) {
@@ -194,7 +249,7 @@ private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesPro
 
             context.identity_matrix ();
             context.scale (0.1875 / SCREENSHOT_WIDTH * width, 0.1875 / SCREENSHOT_HEIGHT * height);
-            var grid = new Cairo.Pattern.for_surface (new Cairo.ImageSurface.from_png (get_pixmap ("boxes-grid.png")));
+            var grid = new Cairo.Pattern.for_surface (grid_surface);
             grid.set_extend (Cairo.Extend.REPEAT);
             context.set_source_rgba (0, 0, 0, 1);
             context.set_operator (Cairo.Operator.OVER);
diff --git a/src/remote-machine.vala b/src/remote-machine.vala
index 77196b9..87ac3cb 100644
--- a/src/remote-machine.vala
+++ b/src/remote-machine.vala
@@ -11,7 +11,8 @@ private class Boxes.RemoteMachine: Boxes.Machine, Boxes.IPropertiesProvider {
 
         config = new DisplayConfig (source);
         source.bind_property ("name", this, "name", BindingFlags.DEFAULT);
-        update_screenshot.begin ();
+
+        load_screenshot ();
     }
 
     public override void connect_display () {
@@ -30,32 +31,6 @@ private class Boxes.RemoteMachine: Boxes.Machine, Boxes.IPropertiesProvider {
         }
     }
 
-    public override string get_screenshot_filename (string ext = "jpg") {
-        return base.get_screenshot_filename (ext);
-    }
-
-    public override void disconnect_display () {
-        if (display == null)
-            return;
-
-        App.app.display_page.remove_display ();
-
-        if (display != null) {
-            try {
-                var pixbuf = display.get_pixbuf (0);
-                if (pixbuf != null) {
-                    pixbuf.save (get_screenshot_filename (), "jpeg");
-                    update_screenshot ();
-                }
-            } catch (GLib.Error err) {
-                warning (err.message);
-            }
-
-            display.disconnect_it ();
-            display = null;
-        }
-    }
-
     public override List<Pair<string, Widget>> get_properties (Boxes.PropertiesPage page) {
         var list = new List<Pair<string, Widget>> ();
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]