123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- #!/usr/bin/python
- #
- # The Qubes OS Project, https://www.qubes-os.org/
- #
- # Copyright (C) 2017 boring-stuff <boring-stuff@users.noreply.github.com>
- #
- # This library is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 2.1 of the License, or (at your option) any later version.
- #
- # This library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with this library; if not, see <https://www.gnu.org/licenses/>.
- #
- import itertools
- # pylint: disable=import-error,wrong-import-position
- import gi
- gi.require_version('Gtk', '3.0')
- from gi.repository import Gtk, Gdk, GdkPixbuf, GObject, GLib
- # pylint: enable=import-error
- from qubespolicy.utils import sanitize_domain_name
- # pylint: enable=wrong-import-position
- class VMListModeler:
- def __init__(self, domains_info=None):
- self._entries = {}
- self._domains_info = domains_info
- self._icons = {}
- self._icon_size = 16
- self._theme = Gtk.IconTheme.get_default()
- self._create_entries()
- def _get_icon(self, name):
- if name not in self._icons:
- try:
- icon = self._theme.load_icon(name, self._icon_size, 0)
- except GLib.Error: # pylint: disable=catching-non-exception
- icon = self._theme.load_icon("edit-find", self._icon_size, 0)
- self._icons[name] = icon
- return self._icons[name]
- def _create_entries(self):
- for name, vm in self._domains_info.items():
- if name.startswith('$dispvm:'):
- vm_name = name[len('$dispvm:'):]
- dispvm = True
- else:
- vm_name = name
- dispvm = False
- sanitize_domain_name(vm_name, assert_sanitized=True)
- icon = self._get_icon(vm.get('icon', None))
- if dispvm:
- display_name = 'Disposable VM ({})'.format(vm_name)
- else:
- display_name = vm_name
- self._entries[display_name] = {
- 'api_name': name,
- 'icon': icon,
- 'vm': vm}
- def _get_valid_qube_name(self, combo, entry_box, whitelist):
- name = None
- if combo and combo.get_active_id():
- selected = combo.get_active_id()
- if selected in self._entries and \
- self._entries[selected]['api_name'] in whitelist:
- name = selected
- if not name and entry_box:
- typed = entry_box.get_text()
- if typed in self._entries and \
- self._entries[typed]['api_name'] in whitelist:
- name = typed
- return name
- def _combo_change(self, selection_trigger, combo, entry_box, whitelist):
- data = None
- name = self._get_valid_qube_name(combo, entry_box, whitelist)
- if name:
- entry = self._entries[name]
- data = entry['api_name']
- if entry_box:
- entry_box.set_icon_from_pixbuf(
- Gtk.EntryIconPosition.PRIMARY, entry['icon'])
- else:
- if entry_box:
- entry_box.set_icon_from_stock(
- Gtk.EntryIconPosition.PRIMARY, "gtk-find")
- if selection_trigger:
- selection_trigger(data)
- def _entry_activate(self, activation_trigger, combo, entry_box, whitelist):
- name = self._get_valid_qube_name(combo, entry_box, whitelist)
- if name:
- activation_trigger(entry_box)
- def apply_model(self, destination_object, vm_list,
- selection_trigger=None, activation_trigger=None):
- if isinstance(destination_object, Gtk.ComboBox):
- list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf)
- for entry_no, display_name in zip(itertools.count(),
- sorted(self._entries)):
- entry = self._entries[display_name]
- if entry['api_name'] in vm_list:
- list_store.append([entry_no, display_name, entry['icon']])
- destination_object.set_model(list_store)
- destination_object.set_id_column(1)
- icon_column = Gtk.CellRendererPixbuf()
- destination_object.pack_start(icon_column, False)
- destination_object.add_attribute(icon_column, "pixbuf", 2)
- destination_object.set_entry_text_column(1)
- if destination_object.get_has_entry():
- entry_box = destination_object.get_child()
- area = Gtk.CellAreaBox()
- area.pack_start(icon_column, False, False, False)
- area.add_attribute(icon_column, "pixbuf", 2)
- completion = Gtk.EntryCompletion.new_with_area(area)
- completion.set_inline_selection(True)
- completion.set_inline_completion(True)
- completion.set_popup_completion(True)
- completion.set_popup_single_match(False)
- completion.set_model(list_store)
- completion.set_text_column(1)
- entry_box.set_completion(completion)
- if activation_trigger:
- entry_box.connect("activate",
- lambda entry: self._entry_activate(
- activation_trigger,
- destination_object,
- entry,
- vm_list))
- # A Combo with an entry has a text column already
- text_column = destination_object.get_cells()[0]
- destination_object.reorder(text_column, 1)
- else:
- entry_box = None
- text_column = Gtk.CellRendererText()
- destination_object.pack_start(text_column, False)
- destination_object.add_attribute(text_column, "text", 1)
- changed_function = lambda combo: self._combo_change(
- selection_trigger,
- combo,
- entry_box,
- vm_list)
- destination_object.connect("changed", changed_function)
- changed_function(destination_object)
- else:
- raise TypeError(
- "Only expecting Gtk.ComboBox objects to want our model.")
- def apply_icon(self, entry, qube_name):
- if isinstance(entry, Gtk.Entry):
- if qube_name in self._entries:
- entry.set_icon_from_pixbuf(
- Gtk.EntryIconPosition.PRIMARY,
- self._entries[qube_name]['icon'])
- else:
- raise ValueError("The specified source qube does not exist!")
- else:
- raise TypeError(
- "Only expecting Gtk.Entry objects to want our icon.")
- class GtkOneTimerHelper:
- # pylint: disable=too-few-public-methods
- def __init__(self, wait_seconds):
- self._wait_seconds = wait_seconds
- self._current_timer_id = 0
- self._timer_completed = False
- def _invalidate_timer_completed(self):
- self._timer_completed = False
- def _invalidate_current_timer(self):
- self._current_timer_id += 1
- def _timer_check_run(self, timer_id):
- if self._current_timer_id == timer_id:
- self._timer_run(timer_id)
- self._timer_completed = True
- else:
- pass
- def _timer_run(self, timer_id):
- raise NotImplementedError("Not yet implemented")
- def _timer_schedule(self):
- self._invalidate_current_timer()
- GObject.timeout_add(int(round(self._wait_seconds * 1000)),
- self._timer_check_run,
- self._current_timer_id)
- def _timer_has_completed(self):
- return self._timer_completed
- class FocusStealingHelper(GtkOneTimerHelper):
- def __init__(self, window, target_button, wait_seconds=1):
- GtkOneTimerHelper.__init__(self, wait_seconds)
- self._window = window
- self._target_button = target_button
- self._window.connect("window-state-event", self._window_state_event)
- self._target_sensitivity = False
- self._target_button.set_sensitive(self._target_sensitivity)
- def _window_changed_focus(self, window_is_focused):
- self._target_button.set_sensitive(False)
- self._invalidate_timer_completed()
- if window_is_focused:
- self._timer_schedule()
- else:
- self._invalidate_current_timer()
- def _window_state_event(self, window, event):
- assert window == self._window, \
- 'Window state callback called with wrong window'
- changed_focus = event.changed_mask & Gdk.WindowState.FOCUSED
- window_focus = event.new_window_state & Gdk.WindowState.FOCUSED
- if changed_focus:
- self._window_changed_focus(window_focus != 0)
- # Propagate event further
- return False
- def _timer_run(self, timer_id):
- self._target_button.set_sensitive(self._target_sensitivity)
- def request_sensitivity(self, sensitivity):
- if self._timer_has_completed() or not sensitivity:
- self._target_button.set_sensitive(sensitivity)
- self._target_sensitivity = sensitivity
- def can_perform_action(self):
- return self._timer_has_completed()
|