[gnome-boxes] Allow adding spice:// machines
- From: Marc-Andre Lureau <malureau src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes] Allow adding spice:// machines
- Date: Mon, 24 Oct 2011 21:34:54 +0000 (UTC)
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]