[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