diff --git a/glade/manager.glade b/glade/manager.glade
index f96a47c..6f3ed00 100644
--- a/glade/manager.glade
+++ b/glade/manager.glade
@@ -277,6 +277,52 @@
32
utilities-terminal
+
True
False
@@ -305,7 +351,7 @@
10
True
imageLogo
-
+
False
@@ -687,50 +733,4 @@
-
- True
- False
- 10
- 10
- 10
-
-
- Terminal
- 80
- 70
- True
- True
- True
- Launch Qubes system terminal
- imageSystemTerminal
- none
- top
-
-
-
- 1
- 0
-
-
-
-
- Files
- 80
- 70
- True
- True
- True
- Launch Qubes system file browser
- imageSystemFiles
- none
- 0.6600000262260437
- top
-
-
-
- 0
- 0
-
-
-
diff --git a/qubesmanager/manager.py b/qubesmanager/manager.py
index d3d3e21..098fbd8 100644
--- a/qubesmanager/manager.py
+++ b/qubesmanager/manager.py
@@ -14,11 +14,11 @@
# Gtk
import gi
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import Gio
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gdk, Gio
from gi.repository.GdkPixbuf import Pixbuf
+
# Qubes Test Data
import tests.data_vmcollection
qvm_collection = tests.data_vmcollection
@@ -72,6 +72,14 @@ def __init__(self, app):
self.vbox.pack_start(builder.get_object("toolbarMain"), False, False, 0)
self.vbox.pack_start(Gtk.Separator(), False, False, 0)
+ #builder.get_object("toolbuttonHelp")
+
+ #menu = Gio.Menu()
+ #menu.append("Browse Files", "app.device_browse")
+ #menu.append("Eject Device", "app.device_eject")
+ #button.set_menu_model(menu)
+
+
# Header of qube
headerApps = builder.get_object("headerApps")
headerNetworking = builder.get_object("headerNetworking")
@@ -127,8 +135,6 @@ def __init__(self, app):
for qube in qvm_collection.values():
if qube["type"] == "sys":
sys_button = self.create_sys_qube_button(qube)
- sys_button.connect("clicked", self.on_clicked_sys_qube,
- qube["name"])
footerBox.pack_start(sys_button, False, False, 0)
footerSeparator = Gtk.Separator(orientation="vertical")
@@ -140,7 +146,6 @@ def __init__(self, app):
for qube in qvm_collection.values():
if qube["type"] == "dev":
device_button = self.create_device_button(qube)
- device_button.connect("clicked", self.on_clicked_device)
footerBox.pack_start(device_button, False, False, 0)
footerBox.pack_end(builder.get_object("systemButtons"), False, False, 0)
@@ -157,69 +162,108 @@ def create_qube_grid(self):
grid.set_row_homogeneous(True)
# Create Scrollable
- scrollable = Gtk.ScrolledWindow()
- scrollable.set_vexpand(True)
+ scrolled = Gtk.ScrolledWindow()
+ scrolled.set_vexpand(True)
- # IconView
+ # Receive signals from mouse clicks in IconView
+ Gtk.IconView.add_events(self, Gdk.EventMask.BUTTON_PRESS_MASK)
+
+ # IconView of qubes
iconview = Gtk.IconView.new()
iconview.set_model(self.type_filter)
iconview.set_columns(5)
iconview.set_margin(10)
iconview.set_item_padding(15)
-
+
# Choose Icon & Text value in list
iconview.set_pixbuf_column(3)
iconview.set_text_column(1)
# Attach iconview
- scrollable.add(iconview)
- grid.attach(scrollable, 0, 0, 8, 10)
+ scrolled.add(iconview)
+ grid.attach(scrolled, 0, 0, 8, 10)
- # connect to the "item-activated" signal
+ # Make IconView items clickable w/ "item-activated"
+ # iconview.connect('activate-on-single-click',
iconview.connect('item-activated',
- self.on_qube_item_activated,
+ self.on_qube_item_double_click,
self.type_filter)
- iconview.grab_focus()
+ # Single Click
+ iconview.connect('button-press-event',
+ self.on_qube_item_single_click)
- # Show "app" qubes by default
+ # Focus and show "app" qubes
+ iconview.grab_focus()
self.type_filter.refilter()
self.vbox.pack_start(grid, True, True, 0)
def create_sys_qube_button(self, qube):
"""Creates button for system qube"""
+ menu = Gio.Menu()
+
+ # Add state/status items
+ if (qube["is_guid_running"] == True and
+ qube["get_power_state"] == "Running"):
+
+ # Add "restart" if needed
+ if qube["is_outdated"] == True or qube["is_fully_usable"] == False:
+ menu.append("Restart (pending updates)", "app.qube_restart")
+
+ # Add "shutdown" always
+ menu.append("Shutdown", "app.qube_shutdown")
+ else:
+ menu.append("Start", "app.qube_start")
+
+ # TODO: specials actions by device type
+ # - app -> launch applications
+ # - networking -> create proxy
+ # - system -> browse files, drivers?
+
+ # Menu always items
+ menu.append("Backups", "app.qube_backups")
+ menu.append("Options", "app.qube_overview")
+
+ # TODO: determine icon by sys type (wifi, usb, sd, bluetooth)
+ # TODO: determine modify icon by state (running, halted, updateable)
if qube["icon"] != "default":
image = qube["icon"]
else:
image = "qube-32"
+
icon = Gtk.Image.new_from_file("icons/" + image + ".png")
icon.set_margin_bottom(5)
- button = Gtk.Button()
+ button = Gtk.MenuButton()
button.set_label(qube["desc"])
button.set_image_position(Gtk.PositionType.TOP)
button.set_image(icon)
button.set_relief(Gtk.ReliefStyle.NONE)
button.set_size_request(70, 80)
- # qube["get_power_state"]
- # qube["is_fully_usable"]
- # qube["is_guid_running"]
- # qube["is_networked"]
-
+ button.set_menu_model(menu)
+
return button
def create_device_button(self, device):
- """Creates button for device qube"""
+ """Creates button & menu for attached device"""
+ # TODO: determine icon by device type (thumbdrive, printer...)
icon = Gtk.Image.new_from_file("icons/" + device["icon"] + ".png")
icon.set_margin_bottom(5)
-
- button = Gtk.Button()
+
+ button = Gtk.MenuButton()
button.set_label(device["desc"])
button.set_image_position(Gtk.PositionType.TOP)
button.set_image(icon)
button.set_relief(Gtk.ReliefStyle.NONE)
button.set_size_request(70, 80)
-
+
+ # Menu Actions
+ # TODO: determine actions by device type (drive -> browse, printer...)
+ menu = Gio.Menu()
+ menu.append("Browse Files", "app.device_browse")
+ menu.append("Eject Device", "app.device_eject")
+ button.set_menu_model(menu)
+
return button
def replace_qube_header(self, old, new):
@@ -264,9 +308,42 @@ def type_filter_func(self, model, iter, data):
else:
return False
- def on_clicked_qubes_name(self, button):
- print "show About dialog window"
+ def on_clicked_qubes_logo(self, button):
+
+ # AboutDialog
+ about = Gtk.AboutDialog()
+ about.set_position(Gtk.WindowPosition.CENTER)
+
+ # Authors & Documenters
+ # TODO: perhaps automate this / use full list on website?
+ authors = ["Joanna Rutkowska", "Marek Marczykowski",
+ "Wojciech Porczyk", "Rafal Wojdyla", "Patrick Schleizer",
+ "Alexander Tereshkin", "Rafal Wojtczuk"]
+
+ documenters = ["Andrew David Wong", "Hashiko Nukama", "Michael Carbone",
+ "Brennan Novak", "The Qubes OS Community"]
+ logo = Pixbuf.new_from_file("icons/qubes-logo.png")
+
+ # Add values
+ about.set_logo(logo)
+ about.set_program_name("Qubes Manager")
+ about.set_copyright("Copyright \xc2\xa9 2016 Qubes OS")
+ about.set_authors(authors)
+ about.set_documenters(documenters)
+ about.set_website("https://qubes-os.org")
+ about.set_website_label("Qubes OS Website")
+
+ # Connect close about response
+ about.connect("response", self.on_clicked_close_about)
+
+ # Show dialog
+ about.show()
+
+ def on_clicked_close_about(self, action, parameter):
+ action.destroy()
+
+ # Filter "qubes" events
def on_state_combo_changed(self, combo):
"""Event for header ComboBox to filter qubes by state"""
self.current_filter_state = self.filter_states[combo.get_active()]
@@ -303,11 +380,17 @@ def on_clicked_dialog(self, widget, value):
self.search_and_mark(dialog.entry.get_text(), start)
dialog.destroy()
- def on_qube_item_activated(self, icon_view, tree_path, store_item):
- print "Grid button clicked: " + str(tree_path)
-
+ def on_qube_item_double_click(self, icon_view, tree_path, store_item):
+ print "Grid item double clicked (activated): " + str(tree_path)
self.type_filter[tree_path]
+ def on_qube_item_single_click(self, iconview, event):
+ if event.button == 3:
+ print "Grid item single clicked RIGHT"
+ else:
+ print "Grid item single clicked LEFT"
+
+
# Toolbar Buttons
def on_clicked_launch_recipes(self, button):
print "launch Recipes"
@@ -357,14 +440,6 @@ def on_clicked_new_template(self, button):
def on_clicked_clone_template(self, button):
print "clone new template"
- # System Items
- def on_clicked_sys_qube(self, button, qube):
- print "open system qube: %s" % qube
-
- # Device Items
- def on_clicked_device(sel, button):
- print "open device specific app"
-
# Sytem Items
def on_clicked_system_files(self, button):
print "launch system file browser"
@@ -376,7 +451,9 @@ def on_clicked_system_terminal(self, button):
class QubesManager(Gtk.Application):
def __init__(self):
- Gtk.Application.__init__(self)
+ Gtk.Application.__init__(self,
+ application_id="org.invisiblethingslab.qubes",
+ flags=Gio.ApplicationFlags.FLAGS_NONE)
def do_activate(self):
win = ManagerWindow(self)
@@ -385,6 +462,60 @@ def do_activate(self):
def do_startup(self):
Gtk.Application.do_startup(self)
+ # Actions for qubes
+ action_qube_backups = Gio.SimpleAction.new("qube_backups", None)
+ action_qube_backups.connect("activate", self.qube_backups_callback)
+ self.add_action(action_qube_backups)
+
+ action_qube_overview = Gio.SimpleAction.new("qube_overview", None)
+ action_qube_overview.connect("activate", self.qube_overview_callback)
+ self.add_action(action_qube_overview)
+
+ action_qube_start = Gio.SimpleAction.new("qube_start", None)
+ action_qube_start.connect("activate", self.qube_start_callback)
+ self.add_action(action_qube_start)
+
+ action_qube_shutdown = Gio.SimpleAction.new("qube_shutdown", None)
+ action_qube_shutdown.connect("activate", self.qube_shutdown_callback)
+ self.add_action(action_qube_shutdown)
+
+ action_qube_restart = Gio.SimpleAction.new("qube_restart", None)
+ action_qube_restart.connect("activate", self.qube_restart_callback)
+ self.add_action(action_qube_restart)
+
+ # Actions for attached devices
+ action_device_browse = Gio.SimpleAction.new("device_browse", None)
+ action_device_browse.connect("activate", self.device_browse_callback)
+ self.add_action(action_device_browse)
+
+ device_eject = Gio.SimpleAction.new("device_eject", None)
+ device_eject.connect("activate", self.device_eject_callback)
+ self.add_action(device_eject)
+
+ # Callbacks for qubes
+ def qube_backups_callback(self, action, parameter):
+ print("clicked: show qube Backup")
+
+ def qube_overview_callback(self, action, parameter):
+ print("clicked: show qube Overview")
+
+ def qube_start_callback(self, action, parameter):
+ print("clicked: qube Start")
+
+ def qube_shutdown_callback(self, action, parameter):
+ print("clicked: qube Shutdown")
+
+ def qube_restart_callback(self, action, parameter):
+ print("clicked: qube Restart")
+
+ # Callbacks for devices
+ def device_browse_callback(self, action, parameter):
+ print("clicked: Browse Device")
+
+ def device_eject_callback(self, action, parameter):
+ print("clicked: Eject Device")
+
+
app = QubesManager()
exit_status = app.run(sys.argv)
sys.exit(exit_status)
diff --git a/tests/data_vmcollection.py b/tests/data_vmcollection.py
index 08a8c71..3160f45 100644
--- a/tests/data_vmcollection.py
+++ b/tests/data_vmcollection.py
@@ -120,16 +120,16 @@ def values():
"is_fully_usable": True,
"is_guid_running": True,
"is_networked": False,
- "is_outdated": False,
+ "is_outdated": True,
"updateable": False
},{
"type": "sys",
"icon": "sd-card-32",
"name": "sddata",
"desc": "SD Card",
- "get_power_state": "Running",
+ "get_power_state": "Halted",
"is_fully_usable": True,
- "is_guid_running": True,
+ "is_guid_running": False,
"is_networked": False,
"is_outdated": False,
"updateable": False