* 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@xxxxxxxxx> --- 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@xxxxxxxxx> +# +# 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 _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list