[gnome-boxes] Allow adding spice:// machines



commit c189e6526b6044018afaf1c57c58b758535ab99a
Author: Marc-Andrà Lureau <marcandre lureau gmail com>
Date:   Mon Oct 24 23:26:59 2011 +0200

    Allow adding spice:// machines

 src/app.vala           |   22 +++--
 src/collection.vala    |   24 +++--
 src/machine.vala       |  247 +++++++++++++++++++++++++++++++-----------------
 src/spice-display.vala |   10 ++-
 src/util.vala          |   38 ++++++++
 src/wizard.vala        |  123 +++++++++++++++++-------
 6 files changed, 326 insertions(+), 138 deletions(-)
---
diff --git a/src/app.vala b/src/app.vala
index 5b15760..22fb3d9 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -64,8 +64,8 @@ private class Boxes.App: Boxes.UI {
         topbar.label.set_text (category.name);
     }
 
-    private async void setup_libvirt (string uri) {
-        var connection = new GVir.Connection (uri);
+    private async void setup_libvirt (CollectionSource source) {
+        var connection = new GVir.Connection (source.uri);
 
         try {
             yield connection.open_async (null);
@@ -75,7 +75,16 @@ private class Boxes.App: Boxes.UI {
         }
 
         foreach (var domain in connection.get_domains ()) {
-            var machine = new Machine (this, connection, domain);
+            var machine = new LibvirtMachine (source, this, connection, domain);
+            collection.add_item (machine);
+        }
+    }
+
+    public void add_collection_source (CollectionSource source) {
+        if (source.source_type == "libvirt")
+            setup_libvirt (source);
+        else if (source.source_type == "spice") {
+            var machine = new SpiceMachine (source, this);
             collection.add_item (machine);
         }
     }
@@ -106,8 +115,7 @@ private class Boxes.App: Boxes.UI {
                     break;
                 foreach (var file in files) {
                     var source = new CollectionSource.with_file (file.get_name ());
-                    if (source.source_type == "libvirt")
-                        setup_libvirt (source.uri);
+                    add_collection_source (source);
                 }
             }
         } catch (GLib.Error error) {
@@ -171,7 +179,7 @@ private class Boxes.App: Boxes.UI {
             if (current_item is Machine) {
                 var machine = current_item as Machine;
 
-                machine.connect_display = false;
+                machine.disconnect_display ();
                 machine.update_screenshot.begin ();
             }
             notebook.page = Boxes.AppPage.MAIN;
@@ -212,7 +220,7 @@ private class Boxes.App: Boxes.UI {
             if (current_item is Machine) {
                 var machine = current_item as Machine;
 
-                machine.connect_display = true;
+                machine.connect_display ();
                 ui_state = UIState.CREDS;
 
             } else
diff --git a/src/collection.vala b/src/collection.vala
index 1138934..4a6e217 100644
--- a/src/collection.vala
+++ b/src/collection.vala
@@ -38,7 +38,18 @@ private class Boxes.CollectionSource: GLib.Object {
         owned get { return get_string ("source", "uri"); }
         set { keyfile.set_string ("source", "uri", value); }
     }
-    private string? filename;
+
+    private string? _filename;
+    public string? filename {
+        get {
+            if (_filename == null)
+                _filename = make_filename (name);
+            return _filename;
+        }
+        set { _filename = value; }
+    }
+
+    private bool has_file;
 
     construct {
         keyfile = new KeyFile ();
@@ -52,6 +63,7 @@ private class Boxes.CollectionSource: GLib.Object {
 
     public CollectionSource.with_file (string filename) throws GLib.Error {
         this.filename = filename;
+        has_file = true;
         load ();
     }
 
@@ -60,13 +72,9 @@ private class Boxes.CollectionSource: GLib.Object {
                                 KeyFileFlags.KEEP_COMMENTS | KeyFileFlags.KEEP_TRANSLATIONS);
     }
 
-    private void save () {
-        if (filename == null) {
-            filename = make_filename (name);
-            keyfile_save (keyfile, get_pkgconfig_source (filename));
-        } else {
-            keyfile_save (keyfile, get_pkgconfig_source (filename), true);
-        }
+    public void save () {
+        keyfile_save (keyfile, get_pkgconfig_source (filename), has_file);
+        has_file = true;
     }
 
     private string? get_string (string group, string key) {
diff --git a/src/machine.vala b/src/machine.vala
index 2c4b31d..01aca05 100644
--- a/src/machine.vala
+++ b/src/machine.vala
@@ -4,21 +4,11 @@ using Gdk;
 using Gtk;
 using GVir;
 
-private class Boxes.Machine: Boxes.CollectionItem {
+private abstract class Boxes.Machine: Boxes.CollectionItem {
     public override Clutter.Actor actor { get { return machine_actor.actor; } }
     public Boxes.App app;
     public MachineActor machine_actor;
-    public GVir.Domain domain;
-    public GVir.Connection connection;
-    public DomainState state {
-        get {
-            try {
-                return domain.get_info ().state;
-            } catch (GLib.Error error) {
-                return DomainState.NONE;
-            }
-        }
-    }
+    public Boxes.CollectionSource source;
 
     private ulong show_id;
     private ulong disconnected_id;
@@ -26,16 +16,21 @@ private class Boxes.Machine: Boxes.CollectionItem {
     private uint screenshot_id;
 
     private Display? _display;
-    private Display? display {
+    protected Display? display {
         get { return _display; }
         set {
             if (_display != null) {
                 _display.disconnect (show_id);
+                show_id = 0;
                 _display.disconnect (disconnected_id);
+                disconnected_id = 0;
                 _display.disconnect (need_password_id);
+                need_password_id = 0;
             }
 
             _display = value;
+            if (_display == null)
+                return;
 
             show_id = _display.show.connect ((id) => {
                 app.ui_state = Boxes.UIState.DISPLAY;
@@ -64,15 +59,13 @@ private class Boxes.Machine: Boxes.CollectionItem {
         }
     }
 
-    public Machine (Boxes.App app, GVir.Connection connection, GVir.Domain domain) {
+    public Machine (Boxes.CollectionSource source, Boxes.App app, string name) {
         this.app = app;
-        this.connection = connection;
-        this.domain = domain;
+        this.name = name;
+        this.source = source;
 
-        name = domain.get_name ();
         machine_actor = new MachineActor (this);
 
-        set_screenshot_enable (true);
         app.notify["ui-state"].connect (() => {
             if (app.ui_state == UIState.DISPLAY)
                 set_screenshot_enable (false);
@@ -97,75 +90,20 @@ private class Boxes.Machine: Boxes.CollectionItem {
         }
     }
 
-    public async bool take_screenshot () throws GLib.Error {
-        if (state != DomainState.RUNNING &&
-            state != DomainState.PAUSED)
-            return false;
-
-        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);
-
-        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]);
-        } while (length > 0);
-
-        return true;
+    public string get_screenshot_filename (string ext = "ppm") {
+        return get_pkgcache (get_screenshot_prefix () + "-screenshot." + ext);
     }
 
-    private ulong started_id;
-    private bool _connect_display;
-    public bool connect_display {
-        get { return _connect_display; }
-        set {
-            if (_connect_display == value)
-                return;
-
-            if (value && state != DomainState.RUNNING) {
-                if (started_id != 0)
-                    return;
-
-                if (state == DomainState.PAUSED) {
-                    started_id = domain.resumed.connect (() => {
-                        domain.disconnect (started_id);
-                        started_id = 0;
-                        connect_display = true;
-                    });
-                    try {
-                        domain.resume ();
-                    } catch (GLib.Error error) {
-                        warning (error.message);
-                    }
-                } else if (state != DomainState.RUNNING) {
-                    started_id = domain.started.connect (() => {
-                        domain.disconnect (started_id);
-                        started_id = 0;
-                        connect_display = true;
-                    });
-                    try {
-                        domain.start (0);
-                    } catch (GLib.Error error) {
-                        warning (error.message);
-                    }
-                }
-            }
-
-            _connect_display = value;
-            update_display ();
-        }
+    public virtual async bool take_screenshot () throws GLib.Error {
+        return false;
     }
 
-    private string get_screenshot_filename (string ext = "ppm") {
-        var uuid = domain.get_uuid ();
+    public abstract bool is_running ();
+    public abstract string get_screenshot_prefix ();
 
-        return get_pkgcache (uuid + "-screenshot." + ext);
-    }
+    protected bool _connect_display;
+    public abstract void connect_display ();
+    public abstract void disconnect_display ();
 
     public async void update_screenshot (int width = 128, int height = 96) {
         Gdk.Pixbuf? pixbuf = null;
@@ -214,7 +152,7 @@ private class Boxes.Machine: Boxes.CollectionItem {
         context.get_source ().set_filter (Cairo.Filter.BEST); // FIXME: cairo scaling is crap
         context.paint ();
 
-        if (state != DomainState.RUNNING) {
+        if (!is_running ()) {
             context.set_source_rgba (1, 1, 1, 1);
             context.set_operator (Cairo.Operator.HSL_SATURATION);
             context.paint ();
@@ -256,6 +194,80 @@ private class Boxes.Machine: Boxes.CollectionItem {
         return Gdk.pixbuf_get_from_surface (surface, 0, 0, width, height);
     }
 
+    public override void ui_state_changed () {
+        machine_actor.ui_state = ui_state;
+    }
+}
+
+private class Boxes.LibvirtMachine: Boxes.Machine {
+    public GVir.Domain domain;
+    public GVir.Connection connection;
+    public DomainState state {
+        get {
+            try {
+                return domain.get_info ().state;
+            } catch (GLib.Error error) {
+                return DomainState.NONE;
+            }
+        }
+    }
+
+    public override void disconnect_display () {
+        if (_connect_display == false)
+            return;
+
+        _connect_display = false;
+        update_display ();
+    }
+
+    private ulong started_id;
+    public override void connect_display () {
+        if (_connect_display == true)
+            return;
+
+        if (state != DomainState.RUNNING) {
+            if (started_id != 0)
+                return;
+
+            if (state == DomainState.PAUSED) {
+                started_id = domain.resumed.connect (() => {
+                    domain.disconnect (started_id);
+                    started_id = 0;
+                    connect_display ();
+                });
+                try {
+                    domain.resume ();
+                } catch (GLib.Error e) {
+                    warning (e.message);
+                }
+            } else if (state != DomainState.RUNNING) {
+                started_id = domain.started.connect (() => {
+                    domain.disconnect (started_id);
+                    started_id = 0;
+                    connect_display ();
+                });
+                try {
+                    domain.start (0);
+                } catch (GLib.Error e) {
+                    warning (e.message);
+                }
+            }
+        }
+
+        _connect_display = true;
+        update_display ();
+    }
+
+    public LibvirtMachine (CollectionSource source, Boxes.App app,
+                           GVir.Connection connection, GVir.Domain domain) {
+        base (source, app, domain.get_name ());
+
+        this.connection = connection;
+        this.domain = domain;
+
+        set_screenshot_enable (true);
+    }
+
     private void update_display () {
         string type, gport, socket, ghost;
 
@@ -281,12 +293,73 @@ private class Boxes.Machine: Boxes.CollectionItem {
             return;
         }
 
-        if (connect_display)
+        if (_connect_display)
             display.connect_it ();
     }
 
-    public override void ui_state_changed () {
-        machine_actor.ui_state = ui_state;
+    public override string get_screenshot_prefix () {
+        return domain.get_uuid ();
+    }
+
+    public override bool is_running () {
+        return state == DomainState.RUNNING;
+    }
+
+    public override async bool take_screenshot () throws GLib.Error {
+        if (state != DomainState.RUNNING &&
+            state != DomainState.PAUSED)
+            return true;
+
+        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);
+
+        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]);
+        } while (length > 0);
+
+        return true;
+    }
+}
+
+private class Boxes.SpiceMachine: Boxes.Machine {
+
+    public SpiceMachine (CollectionSource source, Boxes.App app) {
+        base (source, app, source.name);
+
+        update_screenshot.begin ();
+    }
+
+    public override void connect_display () {
+        if (_connect_display == true)
+            return;
+
+        display = new SpiceDisplay.with_uri (source.uri);
+        display.connect_it ();
+    }
+
+    public override void disconnect_display () {
+        _connect_display = false;
+
+        if (display != null) {
+            display.disconnect_it ();
+            display = null;
+        }
+    }
+
+    public override string get_screenshot_prefix () {
+        return source.filename;
+    }
+
+    public override bool is_running () {
+        // assume the remote is running for now
+        return true;
     }
 }
 
@@ -330,7 +403,7 @@ private class Boxes.MachineActor: Boxes.UI {
             if (event.keyval == Gdk.Key.KP_Enter ||
                 event.keyval == Gdk.Key.ISO_Enter ||
                 event.keyval == Gdk.Key.Return) {
-                machine.connect_display = true;
+                machine.connect_display ();
                 return true;
             }
 
diff --git a/src/spice-display.vala b/src/spice-display.vala
index 54d4f31..96e9392 100644
--- a/src/spice-display.vala
+++ b/src/spice-display.vala
@@ -6,11 +6,19 @@ private class Boxes.SpiceDisplay: Boxes.Display {
     private Session session;
     private ulong channel_new_id;
 
+    construct {
+        need_password = false;
+    }
+
     public SpiceDisplay (string host, int port) {
         session = new Session ();
         session.port = port.to_string ();
         session.host = host;
-        need_password = false;
+    }
+
+    public SpiceDisplay.with_uri (string uri) {
+        session = new Session ();
+        session.uri = uri;
     }
 
     public override Gtk.Widget get_display (int n) throws Boxes.Error {
diff --git a/src/util.vala b/src/util.vala
index d641db3..abd8c5b 100644
--- a/src/util.vala
+++ b/src/util.vala
@@ -210,4 +210,42 @@ namespace Boxes {
         actor.set_size (-1, -1);
         actor.set_position (-1, -1);
     }
+
+    public class Pair<T1,T2> {
+        public T1 first;
+        public T2 second;
+
+        public Pair (T1 first, T2 second) {
+            this.first = first;
+            this.second = second;
+        }
+    }
+
+    // FIXME: should be replaced with GUri the day it's available.
+    public class Query: GLib.Object {
+        string query;
+        HashTable<string, string?> params;
+
+        construct {
+            params = new HashTable<string, string> (GLib.str_hash, GLib.str_equal);
+        }
+
+        public Query (string query) {
+            this.query = query;
+            parse ();
+        }
+
+        private void parse () {
+            foreach (var p in query.split ("&")) {
+                var pair = p.split ("=");
+                if (pair.length != 2)
+                    continue;
+                params.insert (pair[0], pair[1]);
+            }
+        }
+
+        public new string? get (string key) {
+            return params.lookup (key);
+        }
+    }
 }
diff --git a/src/wizard.vala b/src/wizard.vala
index 9783cc2..9378520 100644
--- a/src/wizard.vala
+++ b/src/wizard.vala
@@ -20,7 +20,7 @@ private enum Boxes.SourcePage {
 
 public delegate void ClickedFunc ();
 
-private class Boxes.Source: GLib.Object {
+private class Boxes.WizardSource: GLib.Object {
     public Gtk.Widget widget { get { return notebook; } }
     private SourcePage _page;
     public SourcePage page {
@@ -30,11 +30,14 @@ private class Boxes.Source: GLib.Object {
             notebook.set_current_page (page);
         }
     }
+    public string uri {
+        get { return url_entry.get_text (); }
+    }
 
     private Gtk.Notebook notebook;
-    public Gtk.Entry url_entry;
+    private Gtk.Entry url_entry;
 
-    public Source () {
+    public WizardSource () {
         notebook = new Gtk.Notebook ();
         notebook.get_style_context ().add_class ("boxes-source-nb");
         notebook.show_tabs = false;
@@ -121,26 +124,42 @@ private class Boxes.WizardSummary: GLib.Object {
     public WizardSummary () {
         table = new Gtk.Table (1, 2, false);
         table.margin = 20;
-        table.row_spacing = 20;
+        table.row_spacing = 10;
         table.column_spacing = 20;
-        var label = new Gtk.Label (_("Will create a new box with the following properties:"));
-        label.xalign = 0.0f;
-        table.attach_defaults (label, 0, 2, 0, 1);
-        current_row = 1;
+
+        clear ();
     }
 
-    public void add_property (string name, string value) {
-        var label = new Gtk.Label (name);
-        label.modify_fg (Gtk.StateType.NORMAL, get_color ("grey"));
-        label.xalign = 1.0f;
-        table.attach_defaults (label, 0, 1, current_row, current_row + 1);
+    public void add_property (string name, string? value) {
+        if (value == null)
+            return;
 
-        label = new Gtk.Label (value);
-        label.modify_fg (Gtk.StateType.NORMAL, get_color ("white"));
-        label.xalign = 0.0f;
-        table.attach_defaults (label, 1, 2, current_row, current_row + 1);
+        var label_name = new Gtk.Label (name);
+        label_name.modify_fg (Gtk.StateType.NORMAL, get_color ("grey"));
+        label_name.xalign = 1.0f;
+        table.attach_defaults (label_name, 0, 1, current_row, current_row + 1);
+
+        var label_value = new Gtk.Label (value);
+        label_value.modify_fg (Gtk.StateType.NORMAL, get_color ("white"));
+        label_value.xalign = 0.0f;
+        table.attach_defaults (label_value, 1, 2, current_row, current_row + 1);
 
         current_row += 1;
+        table.show_all ();
+    }
+
+    public void clear () {
+        foreach (var child in table.get_children ()) {
+            table.remove (child);
+        }
+
+        table.resize (1, 2);
+
+        var label = new Gtk.Label (_("Will create a new box with the following properties:"));
+        label.margin_bottom = 10;
+        label.xalign = 0.0f;
+        table.attach_defaults (label, 0, 2, 0, 1);
+        current_row = 1;
     }
 }
 
@@ -153,14 +172,22 @@ private class Boxes.Wizard: Boxes.UI {
     private Gtk.Notebook notebook;
     private Gtk.Button back_button;
     private Gtk.Button next_button;
-    private Boxes.Source source;
+    private Boxes.WizardSource wizard_source;
     private Boxes.WizardSummary summary;
+    private CollectionSource? source;
 
     private WizardPage _page;
     private WizardPage page {
         get { return _page; }
         set {
-            if (value == WizardPage.LAST) {
+            if (value == WizardPage.REVIEW) {
+                try {
+                    prepare ();
+                } catch (Boxes.Error e) {
+                    warning ("Fixme: %s".printf (e.message));
+                    return;
+                }
+            } else if (value == WizardPage.LAST) {
                 if (!create ())
                     return;
                 app.ui_state = UIState.COLLECTION;
@@ -190,7 +217,7 @@ private class Boxes.Wizard: Boxes.UI {
     construct {
         steps = new GenericArray<Gtk.Label> ();
         steps.length = WizardPage.LAST;
-        source = new Boxes.Source ();
+        wizard_source = new Boxes.WizardSource ();
     }
 
     public Wizard (App app) {
@@ -200,20 +227,46 @@ private class Boxes.Wizard: Boxes.UI {
     }
 
     private bool create () {
-        if (this.source.page == Boxes.SourcePage.URL) {
-            var text = this.source.url_entry.get_text ();
-
-            bool uncertain;
-            var type = ContentType.guess (text, null, out uncertain);
-            if (uncertain) {
-                var uri = Xml.URI.parse (text);
-                if (uri.scheme == "spice" || uri.scheme == "vnc") {
-                    return true;
-                }
-            }
+        if (source == null)
+            return false;
+
+        source.save ();
+        app.add_collection_source (source);
+        return true;
+    }
+
+    private void prepare_with_uri (string text) throws Boxes.Error {
+        bool uncertain;
+
+        var mimetype = ContentType.guess (text, null, out uncertain);
+        var uri = Xml.URI.parse (text);
+
+        if (uncertain) {
+            if (uri.server == null)
+                throw new Boxes.Error.INVALID ("the URI is invalid");
+
+            if (uri.scheme == "spice" || uri.scheme == "vnc") {
+                var query = new Query (uri.query_raw ?? uri.query);
+
+                source = new CollectionSource (uri.server, uri.scheme, text);
+                summary.add_property (_("Type"), uri.scheme.up ());
+                summary.add_property (_("Host"), uri.server.down ());
+                summary.add_property (_("Port"), query.get ("port"));
+                summary.add_property (_("TLS Port"), query.get ("tls-port"));
+            } else
+                throw new Boxes.Error.INVALID ("Unsupported protocol");
+        } else {
+            debug ("FIXME: %s".printf (mimetype));
         }
+    }
 
-        return false;
+    private void prepare () throws Boxes.Error {
+        summary.clear ();
+
+        if (this.wizard_source.page == Boxes.SourcePage.URL ||
+            this.wizard_source.page == Boxes.SourcePage.FILE) {
+            prepare_with_uri (this.wizard_source.uri);
+        }
     }
 
     private void add_step (Gtk.Widget widget, string label, WizardPage page) {
@@ -235,8 +288,9 @@ private class Boxes.Wizard: Boxes.UI {
     private bool skip_page (Boxes.WizardPage page) {
         if (page > Boxes.WizardPage.SOURCE &&
             page < Boxes.WizardPage.REVIEW &&
-            this.source.page == Boxes.SourcePage.URL)
+            this.wizard_source.page == Boxes.SourcePage.URL)
             return true;
+
         return false;
     }
 
@@ -265,7 +319,7 @@ private class Boxes.Wizard: Boxes.UI {
         la = new Gtk.Label (_("Insert operating system installation media or select a source below"));
         la.wrap = true;
         vbox.pack_start (la, false, false);
-        vbox.pack_start (source.widget, false, false);
+        vbox.pack_start (wizard_source.widget, false, false);
         vbox.show_all ();
 
         /* Preparation */
@@ -282,7 +336,6 @@ private class Boxes.Wizard: Boxes.UI {
         vbox = new Gtk.VBox (false, 10);
         add_step (vbox, _("Review"), WizardPage.REVIEW);
         summary = new Boxes.WizardSummary ();
-        summary.add_property (_("Hostname"), "foobar");
         vbox.pack_start (summary.widget, false, false);
         vbox.show_all ();
 



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