def __init__(self, machine): Gtk.Notebook.__init__(self) self.machine = machine self.label = Gtk.Label() self.treeview = CGTreeView() scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self.treeview) scrolledwin.set_property("expand", True) sep = Gtk.Separator() sep.set_orientation(Gtk.Orientation.VERTICAL) self.spinner = Gtk.Spinner() closebutton = Gtk.Button.new_from_icon_name("window-close-symbolic", Gtk.IconSize.BUTTON) closebutton.connect("clicked", self.remove_machine) header = Gtk.HBox() header.pack_start(self.spinner, True, False, 5) header.pack_start(self.label, True, True, 0) header.pack_start(closebutton, True, False, 5) header.show_all() self.grid = Gtk.Grid() self.grid.attach(scrolledwin, 0, 0, 1, 1) self.grid.attach(sep, 1, 0, 1, 2) self.append_page(self.grid, header) # When the user enters a new search term, set a filter for the TreeView to only show rows # matching that text. self.connect("search-changed", self.treeview.set_filter_text) # Set up drag & drop methods and signals. Drag & drop is used with MachineView to allow # to drag and drop processes from one machine to another, initiating a process migration. target_entry = Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 1) drag_actions = Gdk.DragAction.COPY # Processes and control groups can be dragged from the TreeView. self.treeview.connect("drag-data-get", self.__get_dragged_process) self.treeview.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK, [target_entry], drag_actions) # Processes and control groups to be dropped anywhere on a MachineView. This begins the # series of steps to migrate the process to this machine. self.connect("drag-data-received", self.__receive_dragged_process) self.drag_dest_set(Gtk.DestDefaults.ALL, [target_entry], drag_actions) self.update()
class MachineView(Gtk.Notebook): """ A Gtk widget that contains a CGTreeView, as well as a title that shows the machine's hostname, and a few other widgets for scrollbars and such. """ __gsignals__ = { "message": (GObject.SIGNAL_RUN_FIRST, None, (str, Gtk.MessageType)), "search-changed": (GObject.SIGNAL_RUN_FIRST, None, (str,)), } def __init__(self, machine): Gtk.Notebook.__init__(self) self.machine = machine self.label = Gtk.Label() self.treeview = CGTreeView() scrolledwin = Gtk.ScrolledWindow() scrolledwin.add(self.treeview) scrolledwin.set_property("expand", True) sep = Gtk.Separator() sep.set_orientation(Gtk.Orientation.VERTICAL) self.spinner = Gtk.Spinner() closebutton = Gtk.Button.new_from_icon_name("window-close-symbolic", Gtk.IconSize.BUTTON) closebutton.connect("clicked", self.remove_machine) header = Gtk.HBox() header.pack_start(self.spinner, True, False, 5) header.pack_start(self.label, True, True, 0) header.pack_start(closebutton, True, False, 5) header.show_all() self.grid = Gtk.Grid() self.grid.attach(scrolledwin, 0, 0, 1, 1) self.grid.attach(sep, 1, 0, 1, 2) self.append_page(self.grid, header) # When the user enters a new search term, set a filter for the TreeView to only show rows # matching that text. self.connect("search-changed", self.treeview.set_filter_text) # Set up drag & drop methods and signals. Drag & drop is used with MachineView to allow # to drag and drop processes from one machine to another, initiating a process migration. target_entry = Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 1) drag_actions = Gdk.DragAction.COPY # Processes and control groups can be dragged from the TreeView. self.treeview.connect("drag-data-get", self.__get_dragged_process) self.treeview.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK, [target_entry], drag_actions) # Processes and control groups to be dropped anywhere on a MachineView. This begins the # series of steps to migrate the process to this machine. self.connect("drag-data-received", self.__receive_dragged_process) self.drag_dest_set(Gtk.DestDefaults.ALL, [target_entry], drag_actions) self.update() def __get_dragged_process(self, widget, context, selection_data, info, time): """ Store the information about the dragged process into selection_data so it can be migrated. """ model, iter = widget.get_selection().get_selected() name, pid = model.get(iter, CGTreeView.NAME_COL, CGTreeView.PID_COL) data = {"hostname": self.machine.hostname, "name": name, "pid": pid} selection_data.set_text(json.dumps(data), -1) def __receive_dragged_process(self, widget, context, x, y, selection_data, info, time): """Dump the selection data and perform the migration.""" data = json.loads(selection_data.get_text()) machine = machines[data["hostname"]] name = data["name"] pid = data["pid"] thread = threading.Thread(target=self.migrate, args=(machine, pid)) thread.daemon = True thread.start() def remove_machine(self, *_): """Remove this machine from the list of connected machines and close the SSH connection.""" if self.machine.ssh_client: self.machine.ssh_client.close() self.destroy() def refresh(self): """ Reload the data in self.machine and update the view when it arrives. This blocks, so it isn't called from the main GUI thread. """ def before_refresh(): self.spinner.start() self.treeview.set_property("sensitive", False) def after_refresh(): self.spinner.stop() self.treeview.set_property("sensitive", True) self.update() try: GLib.idle_add(before_refresh) self.machine.refresh() except MachineException as e: GLib.idle_add(self.emit, "message", str(e), Gtk.MessageType.ERROR) finally: GLib.idle_add(after_refresh) def migrate(self, target_machine, pid): """ Call the appropriate methods to try to migrate the machine associated with this view, then update the view with the results. """ def before_migrate(): self.spinner.start() def after_migrate(): self.spinner.stop() self.update() try: GLib.idle_add(before_migrate) target_machine.migrate(self.machine, pid) self.machine.refresh() message = "Successfully migrated PID %s from %s to %s" % (pid, target_machine.hostname, self.machine.hostname) GLib.idle_add(self.emit, "message", message, Gtk.MessageType.INFO) except MachineException as e: GLib.idle_add(self.emit, "message", str(e), Gtk.MessageType.ERROR) finally: GLib.idle_add(after_migrate) def update(self): """Update the view with the latest data in self.machine.""" self.label.set_markup("<b>%s</b>" % GLib.markup_escape_text(self.machine.hostname)) self.treeview.cgtree = self.machine.get_cgtree() self.treeview.update() self.show_all()