[virt-tools-list] [PATCH] virtManager: Folder sharing implementation for SPICE session
Jitao Lu
dianlujitao at gmail.com
Sat Dec 28 12:12:34 UTC 2019
* 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>
---
Feel free to improve the patch!
ui/foldershare.ui | 129 +++++++++++++++++++++++++++++++++
ui/vmwindow.ui | 9 +++
virtManager/details/console.py | 8 ++
virtManager/details/viewers.py | 63 ++++++++++++++++
virtManager/foldershare.py | 90 +++++++++++++++++++++++
virtManager/vmwindow.py | 8 ++
6 files changed, 307 insertions(+)
create mode 100644 ui/foldershare.ui
create mode 100644 virtManager/foldershare.py
diff --git a/ui/foldershare.ui b/ui/foldershare.ui
new file mode 100644
index 00000000..8fd3c5e8
--- /dev/null
+++ b/ui/foldershare.ui
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.22"/>
+ <object class="GtkWindow" id="vmm-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_folder_share_delete_event" swapped="no"/>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ <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>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_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>
+ </object>
+</interface>
diff --git a/ui/vmwindow.ui b/ui/vmwindow.ui
index 6c78890a..988293b4 100644
--- a/ui/vmwindow.ui
+++ b/ui/vmwindow.ui
@@ -115,6 +115,15 @@
<signal name="activate" handler="on_details_menu_usb_redirection" swapped="no"/>
</object>
</child>
+ <child>
+ <object class="GtkMenuItem" id="details-menu-folder-sharing">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Share folder</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_details_menu_folder_sharing" swapped="no"/>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/virtManager/details/console.py b/virtManager/details/console.py
index 193e79eb..7c0b7495 100644
--- a/virtManager/details/console.py
+++ b/virtManager/details/console.py
@@ -666,6 +666,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-folder-sharing").set_sensitive(
+ bool(is_viewer and self._viewer and
+ self._viewer.console_has_folder_sharing()))
can_sendkey = (is_viewer and not paused)
for c in self._keycombo_menu.get_children():
@@ -1012,6 +1015,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_folder_sharing(self):
+ return bool(self._viewer and
+ self._viewer.console_has_folder_sharing())
+ def details_viewer_get_folder_share_dialog(self):
+ return self._viewer.console_get_folder_share_dialog()
def details_viewer_get_pixbuf(self):
return self._viewer.console_get_pixbuf()
diff --git a/virtManager/details/viewers.py b/virtManager/details/viewers.py
index 1614ba6d..188ecee1 100644
--- a/virtManager/details/viewers.py
+++ b/virtManager/details/viewers.py
@@ -25,6 +25,7 @@ from virtinst import log
from .sshtunnels import SSHTunnels
from ..baseclass import vmmGObject
+from ..foldershare import vmmFolderShare
##################################
@@ -207,6 +208,11 @@ class Viewer(vmmGObject):
def _has_agent(self):
raise NotImplementedError()
+ def _has_folder_sharing(self):
+ raise NotImplementedError()
+ def _get_folder_share_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_folder_sharing(self):
+ return self._has_folder_sharing()
+ def console_get_folder_share_dialog(self):
+ return self._get_folder_share_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_folder_sharing(self):
+ return False
+ def _get_folder_share_dialog(self):
+ return None
+
#######################
# Connection routines #
@@ -489,6 +505,8 @@ class SpiceViewer(Viewer):
self._main_channel_hids = []
self._display_channel = None
self._usbdev_manager = None
+ self._webdav_channel = None
+ self._folder_share_dialog = None
###################
@@ -628,6 +646,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")
@@ -760,3 +781,45 @@ class SpiceViewer(Viewer):
if c.__class__ is SpiceClientGLib.UsbredirChannel:
return True
return False
+
+ def _has_folder_sharing(self):
+ if not self._spice_session:
+ return False
+
+ return self._webdav_channel is not None
+
+ def _get_folder_share_dialog(self):
+ if not self._spice_session:
+ return
+
+ try:
+ if not self._folder_share_dialog:
+ self._folder_share_dialog = vmmFolderShare()
+ GObject.GObject.bind_property(
+ self._spice_session, "share-dir-ro", self._folder_share_dialog,
+ vmmFolderShare.SHARE_FOLDER_RO, GObject.BindingFlags.BIDIRECTIONAL
+ | GObject.BindingFlags.SYNC_CREATE)
+ GObject.GObject.bind_property(
+ self._spice_session, "shared-dir", self._folder_share_dialog,
+ vmmFolderShare.SHARED_FOLDER, GObject.BindingFlags.BIDIRECTIONAL
+ | GObject.BindingFlags.SYNC_CREATE)
+ self._folder_share_dialog.set_property(
+ vmmFolderShare.SHARE_FOLDER,
+ self._webdav_channel.get_property("port-opened"))
+ self._folder_share_dialog.connect("notify::" + vmmFolderShare.SHARE_FOLDER,
+ self._update_share_folder)
+ except Exception as e:
+ log.error("Error launching 'Share folder' dialog: %s", e)
+
+ return self._folder_share_dialog
+
+ def _update_share_folder(self, dialog, ignore):
+ share = dialog.get_property(vmmFolderShare.SHARE_FOLDER)
+ if share:
+ 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)
diff --git a/virtManager/foldershare.py b/virtManager/foldershare.py
new file mode 100644
index 00000000..c92ccab4
--- /dev/null
+++ b/virtManager/foldershare.py
@@ -0,0 +1,90 @@
+# Copyright (C) 2019 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 gi.repository import GLib, GObject
+
+from .baseclass import vmmGObjectUI
+
+
+class vmmFolderShare(vmmGObjectUI):
+ SHARE_FOLDER = "share-folder"
+ SHARE_FOLDER_RO = "share-folder-ro"
+ SHARED_FOLDER = "shared-folder"
+
+ __gproperties__ = {
+ # 'name': (GObject.TYPE_*,
+ # nickname, long desc, (type related args), mode)
+ # Type related args can be min, max for int (etc.), or default value
+ # for strings and bool
+ SHARE_FOLDER:
+ (GObject.TYPE_BOOLEAN, "Share folder",
+ "Indicates whether to share folder", False, GObject.PARAM_READWRITE),
+ SHARE_FOLDER_RO: (GObject.TYPE_BOOLEAN, "Share folder read-only",
+ "Indicates whether to share folder in read-only",
+ False, GObject.PARAM_READWRITE),
+ SHARED_FOLDER:
+ (GObject.TYPE_STRING, "Shared folder", "Indicates the shared folder",
+ GLib.get_user_special_dir(GLib.USER_DIRECTORY_PUBLIC_SHARE),
+ GObject.PARAM_READWRITE),
+ }
+
+ def __init__(self):
+ vmmGObjectUI.__init__(self, "foldershare.ui", "vmm-folder-share")
+ self._cleanup_on_app_close()
+ self.bind_escape_key_close()
+
+ self.builder.connect_signals({
+ "on_vmm_folder_share_delete_event":
+ self.close,
+ "on_folder_share_close_clicked":
+ self.close,
+ })
+
+ self.share_folder = False
+ self.share_folder_ro = False
+ self.shared_folder = GLib.get_user_special_dir(
+ GLib.USER_DIRECTORY_PUBLIC_SHARE)
+
+ self.share_folder_cb = self.widget("share-folder-cb")
+ GObject.GObject.bind_property(
+ self, self.__class__.SHARE_FOLDER, self.share_folder_cb, "active",
+ GObject.BindingFlags.BIDIRECTIONAL
+ | GObject.BindingFlags.SYNC_CREATE)
+
+ self.share_folder_ro_cb = self.widget("share-folder-ro-cb")
+ GObject.GObject.bind_property(
+ self, self.__class__.SHARE_FOLDER_RO, self.share_folder_ro_cb,
+ "active", GObject.BindingFlags.BIDIRECTIONAL
+ | GObject.BindingFlags.SYNC_CREATE)
+
+ self.shared_folder_fc = self.widget("shared-folder-fc")
+ self.shared_folder_fc.connect(
+ "file-set", lambda fc: self.set_property(
+ self.__class__.SHARED_FOLDER, fc.get_filename()))
+
+ def show(self, parent):
+ self.shared_folder_fc.set_current_folder(self.shared_folder)
+ 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
+
+ # Properties are passed to use with "-" in the name, but python
+ # variables can't be named like that
+ def _sanitize_param_spec_name(self, name):
+ return name.replace("-", "_")
+
+ def do_get_property(self, param_spec):
+ name = self._sanitize_param_spec_name(param_spec.name)
+ return getattr(self, name)
+
+ def do_set_property(self, param_spec, value):
+ name = self._sanitize_param_spec_name(param_spec.name)
+ setattr(self, name, value)
diff --git a/virtManager/vmwindow.py b/virtManager/vmwindow.py
index 54e37399..552a14c7 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_folder_sharing": self.control_vm_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_folder = self.console.details_viewer_has_folder_sharing()
+ self.widget("details-menu-folder-sharing").set_sensitive(can_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_folder_sharing(self, ignore):
+ folder_share_dialog = self.console.details_viewer_get_folder_share_dialog()
+ folder_share_dialog.show(self.topwin)
+
def _take_screenshot(self):
image = self.console.details_viewer_get_pixbuf()
--
2.24.1
More information about the virt-tools-list
mailing list