Skip to content

Commit

Permalink
Added notifications and information about VM volumes being full
Browse files Browse the repository at this point in the history
The qui-disk-space widget will now show notifications when a VM's
storage space is close to running out. Also information about this
will be displayed in the widget menu itself.

references QubesOS/qubes-issues#1872
  • Loading branch information
marmarta committed May 28, 2020
1 parent 1c67717 commit 20bfa25
Showing 1 changed file with 181 additions and 24 deletions.
205 changes: 181 additions & 24 deletions qui/tray/disk_space.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# pylint: disable=wrong-import-position,import-error
import sys
import subprocess
import gi
gi.require_version('Gtk', '3.0') # isort:skip
from gi.repository import Gtk, GObject, Gio # isort:skip
from gi.repository import Gtk, GObject, Gio, GLib # isort:skip
from qubesadmin import Qubes
from qubesadmin.utils import size_to_human

Expand All @@ -16,9 +17,118 @@
URGENT_WARN_LEVEL = 0.95


class VMUsage:
def __init__(self, vm):
self.vm = vm
self.problem_volumes = {}

self.check_usage()

def check_usage(self):
self.problem_volumes = {}
volumes_to_check = ['private']
if not hasattr(self.vm, 'template'):
volumes_to_check.append('root')
for volume_name in volumes_to_check:
if volume_name in self.vm.volumes:
size = self.vm.volumes[volume_name].size
usage = self.vm.volumes[volume_name].usage
if size > 0 and usage / size > WARN_LEVEL:
self.problem_volumes[volume_name] = usage / size


class VMUsageData:
def __init__(self, qubes_app):
self.qubes_app = qubes_app
self.problematic_vms = []

self.__populate_vms()

def __populate_vms(self):
for vm in self.qubes_app.domains:
if vm.is_running():
usage_data = VMUsage(vm)
if usage_data.problem_volumes:
self.problematic_vms.append(usage_data)

def get_vms_widgets(self):
for vm_usage in self.problematic_vms:
yield self.__create_widgets(vm_usage)

@staticmethod
def __create_widgets(vm_usage):
vm = vm_usage.vm

# icon widget
try:
icon = vm.icon
except AttributeError:
icon = vm.label.icon
icon_vm = Gtk.IconTheme.get_default().load_icon(icon, 16, 0)
icon_img = Gtk.Image.new_from_pixbuf(icon_vm)

# description widget
label_widget = Gtk.Label(xalign=0)

label_contents = []

for volume_name, usage in vm_usage.problem_volumes.items():
label_contents.append('volume <b>{}</b> is {:.1%} full'.format(
volume_name, usage))

label_text = "<b>{}</b>: ".format(vm.name) + ", ".join(label_contents)
label_widget.set_markup(label_text)

return vm, icon_img, label_widget


class SettingsItem(Gtk.MenuItem):
def __init__(self, vm):
super().__init__()
self.vm = vm

self.set_label(_('Open Qube Settings'))

self.connect('activate', launch_preferences_dialog, self.vm.name)


def launch_preferences_dialog(_, vm):
vm = str(vm).strip('\'')
subprocess.Popen(['qubes-vm-settings', vm])


class NeverNotifyItem(Gtk.CheckMenuItem):
def __init__(self, vm):
super().__init__()
self.vm = vm

self.set_label(_('Do not show notifications about this qube'))

self.set_active(self.vm.features.get('disk-space-not-notify', False))

self.connect('toggled', self.toggle_state)

def toggle_state(self, _item):
if self.get_active():
self.vm.features['disk-space-not-notify'] = 1
else:
del self.vm.features['disk-space-not-notify']


class VMMenu(Gtk.Menu):
def __init__(self, vm):
super().__init__()
self.vm = vm

self.add(NeverNotifyItem(self.vm))
self.add(SettingsItem(self.vm))

self.show_all()


class PoolUsageData:
def __init__(self):
self.qubes_app = Qubes()
def __init__(self, qubes_app):
self.qubes_app = qubes_app

self.pools = []
self.total_size = 0
Expand Down Expand Up @@ -86,7 +196,6 @@ def __create_box(pool):

name_box.pack_start(metadata_name, True, True, 0)


percentage = pool.usage/pool.size

percentage_use = Gtk.Label()
Expand Down Expand Up @@ -141,15 +250,34 @@ def colored_percentage(value):
return result


def emit_notification(gtk_app, title, text, vm=None):
notification = Gio.Notification.new(title)
notification.set_priority(Gio.NotificationPriority.HIGH)
notification.set_body(text)
notification.set_icon(Gio.ThemedIcon.new('dialog-warning'))

if vm:
notification.add_button('Open qube settings',
"app.prefs::{}".format(vm.name))

gtk_app.send_notification(None, notification)


class DiskSpace(Gtk.Application):
def __init__(self, **properties):
super().__init__(**properties)

self.warned = False

self.qubes_app = Qubes()

self.set_application_id("org.qubes.qui.tray.DiskSpace")
self.register()

prefs_action = Gio.SimpleAction.new("prefs", GLib.VariantType.new("s"))
prefs_action.connect("activate", launch_preferences_dialog)
self.add_action(prefs_action)

self.icon = Gtk.StatusIcon()
self.icon.connect('button-press-event', self.make_menu)
self.refresh_icon()
Expand All @@ -159,47 +287,52 @@ def __init__(self, **properties):
Gtk.main()

def refresh_icon(self):
pool_data = PoolUsageData()
warning = pool_data.get_warning()
pool_data = PoolUsageData(self.qubes_app)
vm_data = VMUsageData(self.qubes_app)
pool_warning = pool_data.get_warning()
vm_warning = vm_data.problematic_vms

if warning:
if pool_warning:
self.icon.set_from_icon_name("dialog-warning")
text = _("<b>Qubes Disk Space Monitor</b>\nWARNING! You are "
"running out of disk space.") + ''.join(warning)
"running out of disk space.") + ''.join(pool_warning)
self.icon.set_tooltip_markup(text)

if not self.warned:
notification = Gio.Notification.new(_("Disk usage warning!"))
notification.set_priority(Gio.NotificationPriority.HIGH)
notification.set_body(
_("You are running out of disk space.") + ''.join(warning))
notification.set_icon(
Gio.ThemedIcon.new('dialog-warning'))

self.send_notification(None, notification)
emit_notification(
self,
_("Disk usage warning!"),
_("You are running out of disk space.") + ''.join(
pool_warning))
self.warned = True

else:
self.icon.set_from_icon_name("drive-harddisk")
self.icon.set_tooltip_markup(
_('<b>Qubes Disk Space Monitor</b>\nView free disk space.'))
self.warned = False

if vm_warning:
for vm_data in vm_warning:
vm = vm_data.vm
if not vm.features.get('disk-space-not-notify', False):
emit_notification(
self,
_("Qube usage warning"),
_("Qube {} is running out of storage space.".format(
vm.name)),
vm=vm)

return True # needed for Gtk to correctly loop the function

def make_menu(self, _unused, _event):
pool_data = PoolUsageData()
pool_data = PoolUsageData(self.qubes_app)
vm_data = VMUsageData(self.qubes_app)

menu = Gtk.Menu()

menu.append(self.make_top_box(pool_data))

title_label = Gtk.Label(xalign=0)
title_label.set_markup(_("<b>Volumes</b>"))
title_menu_item = Gtk.MenuItem()
title_menu_item.add(title_label)
title_menu_item.set_sensitive(False)
menu.append(title_menu_item)
menu.append(self.make_title_item('Volumes'))

grid = Gtk.Grid()
col_no = 0
Expand All @@ -215,11 +348,35 @@ def make_menu(self, _unused, _event):
grid_menu_item.set_sensitive(False)
menu.append(grid_menu_item)

if vm_data.problematic_vms:
menu.append(self.make_title_item('Qubes warnings'))

for (vm, label1, label2) in vm_data.get_vms_widgets():
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
hbox.pack_start(label1, False, False, 0)
hbox.pack_start(label2, False, False, 5)

vm_menu_item = Gtk.MenuItem()
vm_menu_item.add(hbox)

vm_menu_item.set_submenu(VMMenu(vm))

menu.append(vm_menu_item)

menu.set_reserve_toggle_size(False)

menu.show_all()
menu.popup_at_pointer(None) # use current event

@staticmethod
def make_title_item(text):
label = Gtk.Label(xalign=0)
label.set_markup(_("<b>{}</b>".format(text)))
menu_item = Gtk.MenuItem()
menu_item.add(label)
menu_item.set_sensitive(False)
return menu_item

@staticmethod
def make_top_box(pool_data):
grid = Gtk.Grid()
Expand Down

0 comments on commit 20bfa25

Please sign in to comment.