[PATCH] virtManager: Folder sharing implementation for SPICE session
Jitao Lu
dianlujitao at gmail.com
Sat Jul 25 13:30:36 UTC 2020
* This implements folder sharing for the built-in Spice client, tested
working with Win10 guest.
* The basic idea is taken from virt-viewer.
Signed-off-by: Jitao Lu <dianlujitao at gmail.com>
---
ui/spicewebdav.ui | 129 +++++++++++++++++++++++++++++
ui/vmwindow.ui | 17 +++-
virtManager/details/console.py | 8 ++
virtManager/details/spicewebdav.py | 60 ++++++++++++++
virtManager/details/viewers.py | 68 +++++++++++++++
virtManager/vmwindow.py | 8 ++
6 files changed, 286 insertions(+), 4 deletions(-)
create mode 100644 ui/spicewebdav.ui
create mode 100644 virtManager/details/spicewebdav.py
diff --git a/ui/spicewebdav.ui b/ui/spicewebdav.ui
new file mode 100644
index 00000000..a2a372b1
--- /dev/null
+++ b/ui/spicewebdav.ui
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.36.0 -->
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <object class="GtkWindow" id="vmm-spice-webdav-folder-share">
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="title" translatable="yes">Share folder</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <signal name="delete-event" handler="on_vmm_spice_webdav_folder_share_delete_event" swapped="no"/>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="share-folder-cb">
+ <property name="label" translatable="yes">Share folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="share-folder-ro-cb">
+ <property name="label" translatable="yes">Read-only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="shared-folder-fc">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>SPICE WebDAV folder sharing</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="folder-share-close">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_vmm_spice_webdav_folder_share_close_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/ui/vmwindow.ui b/ui/vmwindow.ui
index 6c78890a..a9330c9a 100644
--- a/ui/vmwindow.ui
+++ b/ui/vmwindow.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
+<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkAccelGroup" id="accelgroup1"/>
@@ -18,9 +18,6 @@
</accel-groups>
<signal name="configure-event" handler="on_vmm_details_configure_event" swapped="no"/>
<signal name="delete-event" handler="on_vmm_details_delete_event" swapped="no"/>
- <child type="titlebar">
- <placeholder/>
- </child>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
@@ -115,6 +112,15 @@
<signal name="activate" handler="on_details_menu_usb_redirection" swapped="no"/>
</object>
</child>
+ <child>
+ <object class="GtkMenuItem" id="details-menu-spice-webdav-folder-sharing">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_SPICE WebDAV folder sharing</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_details_menu_spice_webdav_folder_sharing" swapped="no"/>
+ </object>
+ </child>
</object>
</child>
</object>
@@ -821,5 +827,8 @@
</child>
</object>
</child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
</object>
</interface>
diff --git a/virtManager/details/console.py b/virtManager/details/console.py
index 062a019c..613cb63a 100644
--- a/virtManager/details/console.py
+++ b/virtManager/details/console.py
@@ -667,6 +667,9 @@ class vmmConsolePages(vmmGObjectUI):
bool(is_viewer and self._viewer and
self._viewer.console_has_usb_redirection() and
self.vm.has_spicevmc_type_redirdev()))
+ self.widget("details-menu-spice-webdav-folder-sharing").set_sensitive(
+ bool(is_viewer and self._viewer and
+ self._viewer.console_has_spice_webdav_folder_sharing()))
can_sendkey = (is_viewer and not paused)
for c in self._keycombo_menu.get_children():
@@ -1013,6 +1016,11 @@ class vmmConsolePages(vmmGObjectUI):
self._viewer.console_has_usb_redirection())
def details_viewer_get_usb_widget(self):
return self._viewer.console_get_usb_widget()
+ def details_viewer_has_spice_webdav_folder_sharing(self):
+ return bool(self._viewer and
+ self._viewer.console_has_spice_webdav_folder_sharing())
+ def details_viewer_get_spice_webdav_folder_sharing_dialog(self):
+ return self._viewer.console_get_spice_webdav_folder_sharing_dialog()
def details_viewer_get_pixbuf(self):
return self._viewer.console_get_pixbuf()
diff --git a/virtManager/details/spicewebdav.py b/virtManager/details/spicewebdav.py
new file mode 100644
index 00000000..1774675b
--- /dev/null
+++ b/virtManager/details/spicewebdav.py
@@ -0,0 +1,60 @@
+# Copyright (C) 2019-2020 Jitao Lu <dianlujitao at gmail.com>
+#
+# This work is licensed under the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+from ..baseclass import vmmGObjectUI
+
+
+class vmmSpiceWebdav(vmmGObjectUI):
+ SHARE_FOLDER = "share-folder"
+ SHARE_FOLDER_RO = "share-folder-ro"
+ SHARED_FOLDER = "shared-folder"
+
+ __gsignals__ = {
+ SHARE_FOLDER: (vmmGObjectUI.RUN_FIRST, None, [bool]),
+ SHARE_FOLDER_RO: (vmmGObjectUI.RUN_FIRST, None, [bool]),
+ SHARED_FOLDER: (vmmGObjectUI.RUN_FIRST, None, [str]),
+ }
+
+ def __init__(self, enabled, ro, folder):
+ vmmGObjectUI.__init__(self, "spicewebdav.ui",
+ "vmm-spice-webdav-folder-share")
+ self._cleanup_on_app_close()
+ self.bind_escape_key_close()
+
+ self.builder.connect_signals({
+ "on_vmm_spice_webdav_folder_share_delete_event":
+ self.close,
+ "on_vmm_spice_webdav_folder_share_close_clicked":
+ self.close,
+ })
+
+ share_folder_cb = self.widget("share-folder-cb")
+ share_folder_cb.set_active(enabled)
+ share_folder_cb.connect(
+ "toggled", lambda src: self.emit(self.__class__.SHARE_FOLDER,
+ src.get_active()))
+
+ share_folder_ro_cb = self.widget("share-folder-ro-cb")
+ share_folder_ro_cb.set_active(ro)
+ share_folder_ro_cb.connect(
+ "toggled", lambda src: self.emit(self.__class__.SHARE_FOLDER_RO,
+ src.get_active()))
+
+ shared_folder_fc = self.widget("shared-folder-fc")
+ shared_folder_fc.set_current_folder(folder)
+ shared_folder_fc.connect(
+ "file-set", lambda src: self.emit(self.__class__.SHARED_FOLDER,
+ src.get_filename()))
+
+ def show(self, parent):
+ self.topwin.set_transient_for(parent)
+ self.topwin.present()
+
+ def _cleanup(self):
+ pass
+
+ def close(self, ignore1=None, ignore2=None):
+ self.topwin.hide()
+ return 1
diff --git a/virtManager/details/viewers.py b/virtManager/details/viewers.py
index 4ae0e668..edaec035 100644
--- a/virtManager/details/viewers.py
+++ b/virtManager/details/viewers.py
@@ -24,6 +24,7 @@ except (ValueError, ImportError):
from virtinst import log
from .sshtunnels import SSHTunnels
+from .spicewebdav import vmmSpiceWebdav
from ..baseclass import vmmGObject
@@ -207,6 +208,11 @@ class Viewer(vmmGObject):
def _has_agent(self):
raise NotImplementedError()
+ def _has_spice_webdav_folder_sharing(self):
+ raise NotImplementedError()
+ def _get_spice_webdav_folder_sharing_dialog(self):
+ raise NotImplementedError()
+
####################################
# APIs accessed by vmmConsolePages #
@@ -270,6 +276,11 @@ class Viewer(vmmGObject):
def console_has_agent(self):
return self._has_agent()
+ def console_has_spice_webdav_folder_sharing(self):
+ return self._has_spice_webdav_folder_sharing()
+ def console_get_spice_webdav_folder_sharing_dialog(self):
+ return self._get_spice_webdav_folder_sharing_dialog()
+
def console_remove_display_from_widget(self, widget):
if self._display and self._display in widget.get_children():
widget.remove(self._display)
@@ -437,6 +448,11 @@ class VNCViewer(Viewer):
def _has_agent(self):
return False
+ def _has_spice_webdav_folder_sharing(self):
+ return False
+ def _get_spice_webdav_folder_sharing_dialog(self):
+ return None
+
#######################
# Connection routines #
@@ -492,6 +508,8 @@ class SpiceViewer(Viewer):
self._main_channel_hids = []
self._display_channel = None
self._usbdev_manager = None
+ self._webdav_channel = None
+ self._webdav_folder_sharing_dialog = None
###################
@@ -631,6 +649,9 @@ class SpiceViewer(Viewer):
SpiceClientGLib.RecordChannel] and
not self._audio):
self._audio = SpiceClientGLib.Audio.get(self._spice_session, None)
+ elif (type(channel) == SpiceClientGLib.WebdavChannel and
+ not self._webdav_channel):
+ self._webdav_channel = channel
def _agent_connected_cb(self, src, val):
self.emit("agent-connected")
@@ -653,6 +674,9 @@ class SpiceViewer(Viewer):
self._close_main_channel()
self._usbdev_manager = None
+ self._webdav_channel = None
+ self._webdav_folder_sharing_dialog = None
+
def _is_open(self):
return self._spice_session is not None
@@ -763,3 +787,47 @@ class SpiceViewer(Viewer):
if c.__class__ is SpiceClientGLib.UsbredirChannel:
return True
return False
+
+ def _has_spice_webdav_folder_sharing(self):
+ return self._webdav_channel is not None
+
+ def _get_spice_webdav_folder_sharing_dialog(self):
+ if not self._spice_session:
+ return
+
+ try:
+ if not self._webdav_folder_sharing_dialog:
+ self._webdav_folder_sharing_dialog = vmmSpiceWebdav(
+ self._webdav_channel.get_property("port-opened"),
+ self._spice_session.get_property("share-dir-ro"),
+ self._spice_session.get_property("shared-dir"))
+
+ self._webdav_folder_sharing_dialog.connect(
+ vmmSpiceWebdav.SHARE_FOLDER_RO,
+ self._on_webdav_folder_share_ro_toggled)
+ self._webdav_folder_sharing_dialog.connect(
+ vmmSpiceWebdav.SHARE_FOLDER,
+ self._on_webdav_folder_share_toggled)
+ self._webdav_folder_sharing_dialog.connect(
+ vmmSpiceWebdav.SHARED_FOLDER,
+ self._on_webdav_folder_share_changed)
+ except Exception as e:
+ log.error("Error launching 'Share folder' dialog: %s", e)
+
+ return self._webdav_folder_sharing_dialog
+
+ def _on_webdav_folder_share_ro_toggled(self, ignore, enabled):
+ self._spice_session.set_property("share-dir-ro", enabled)
+
+ def _on_webdav_folder_share_toggled(self, ignore, enabled):
+ if enabled:
+ log.debug("Enabling folder sharing, shared dir: %s read-only: %r",
+ self._spice_session.get_property("shared-dir"),
+ self._spice_session.get_property("share-dir-ro"))
+ self._webdav_channel.connect()
+ else:
+ log.debug("Disabling folder sharing")
+ self._webdav_channel.disconnect(SpiceClientGLib.ChannelEvent.NONE)
+
+ def _on_webdav_folder_share_changed(self, ignore, folder):
+ self._spice_session.set_property("shared-dir", folder)
diff --git a/virtManager/vmwindow.py b/virtManager/vmwindow.py
index 54e37399..9e8a2c85 100644
--- a/virtManager/vmwindow.py
+++ b/virtManager/vmwindow.py
@@ -111,6 +111,7 @@ class vmmVMWindow(vmmGObjectUI):
"on_details_menu_virtual_manager_activate": self.control_vm_menu,
"on_details_menu_screenshot_activate": self.control_vm_screenshot,
"on_details_menu_usb_redirection": self.control_vm_usb_redirection,
+ "on_details_menu_spice_webdav_folder_sharing": self.control_vm_spice_webdav_folder_sharing,
"on_details_menu_view_toolbar_activate": self.toggle_toolbar,
"on_details_menu_view_manager_activate": self.view_manager,
"on_details_menu_view_details_toggled": self.details_console_changed,
@@ -472,6 +473,9 @@ class vmmVMWindow(vmmGObjectUI):
self.vm.has_spicevmc_type_redirdev())
self.widget("details-menu-usb-redirection").set_sensitive(can_usb)
+ can_share_folder = self.console.details_viewer_has_spice_webdav_folder_sharing()
+ self.widget("details-menu-spice-webdav-folder-sharing").set_sensitive(can_share_folder)
+
def control_vm_run(self, src_ignore):
if self._details.vmwindow_has_unapplied_changes():
return
@@ -501,6 +505,10 @@ class vmmVMWindow(vmmGObjectUI):
widget=spice_usbdev_widget,
buttons=Gtk.ButtonsType.CLOSE)
+ def control_vm_spice_webdav_folder_sharing(self, ignore):
+ folder_sharing_dialog = self.console.details_viewer_get_spice_webdav_folder_sharing_dialog()
+ folder_sharing_dialog.show(self.topwin)
+
def _take_screenshot(self):
image = self.console.details_viewer_get_pixbuf()
--
2.27.0
More information about the virt-tools-list
mailing list