Skip to content

Commit 6a04fc5

Browse files
committed
Merge remote-tracking branch 'origin/pr/243'
* origin/pr/243: Added root terminal to Domains tray icon menu Pull request description: This pull request adds a subtle "Run Root Terminal" option to the Domains tray icon menu, which is very useful when not using `qubes-core-agent-passwordless-root`. As I've done before, I've tried my best to not change anything about the user experience of the application for most users (as I expect most users to run with `qubes-core-agent-passwordless-root`). The behavior after this patch is that holding the shift key changes the "Run Terminal" command into a "Run Root Terminal" command. Looking at existing issues, the closest I found was [QubesOS/qubes-issues#9512](QubesOS/qubes-issues#9512), but the use case is different as it is a simpler way of making `qubes-core-agent-passwordless-root` togglable. This is instead a simplified root terminal access, over having to open a Dom0 terminal and start it with `qvm-run`. ~~*Extra considerations*~~: - ~~I'm not at all familiar with Gtk, and I'm feeling a bit uncertain that signals was the right way to go.~~ *No longer using signals* - ~~There is a race when smashing the shift key. I don't think it's a performance issue, and not really a UX issue (as the text matches the action that will be performed when terminal is pressed).~~ *I had forgotten that my keyboard has an automatic hold function for the shift key 🤦
2 parents 7652c60 + 4d8f52f commit 6a04fc5

1 file changed

Lines changed: 68 additions & 6 deletions

File tree

qui/tray/domains.py

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import qui.utils
2525

2626
gi.require_version("Gtk", "3.0") # isort:skip
27-
from gi.repository import Gio, Gtk, GLib, GdkPixbuf # isort:skip
27+
from gi.repository import Gdk, Gio, Gtk, GLib, GdkPixbuf # isort:skip
2828

2929
import gbulb
3030

@@ -114,7 +114,11 @@ def __init__(self, label, img=None, icon_cache=None, icon_name=None):
114114
box.pack_start(placeholder, False, False, 0)
115115

116116
# Add a label to the menu item
117-
label_widget = Gtk.Label(label=label, xalign=0)
117+
label_widget = label
118+
if isinstance(label_widget, Gtk.Label):
119+
label_widget.set_xalign(0)
120+
else:
121+
label_widget = Gtk.Label(label=label, xalign=0)
118122
box.pack_start(label_widget, True, True, 0)
119123

120124
# Add the box to the menu item
@@ -295,17 +299,35 @@ async def perform_action(self):
295299
class RunTerminalItem(VMActionMenuItem):
296300
"""Run Terminal menu Item. When activated runs a terminal emulator."""
297301

298-
def __init__(self, vm, icon_cache):
302+
def __init__(self, vm, icon_cache, as_root=False):
303+
label = Gtk.Label(label=RunTerminalItem.dynamic_label(as_root))
299304
super().__init__(
300305
vm,
301-
label=_("Run Terminal"),
306+
label=label,
302307
icon_cache=icon_cache,
303308
icon_name="terminal",
304309
)
310+
self.as_root = as_root
311+
self.label = label
312+
313+
@staticmethod
314+
def dynamic_label(as_root):
315+
if as_root:
316+
return _("Run Root Terminal")
317+
return _("Run Terminal")
318+
319+
def set_as_root(self, as_root):
320+
self.as_root = as_root
321+
self.label.set_text(RunTerminalItem.dynamic_label(self.as_root))
305322

306323
async def perform_action(self):
324+
service_args = {}
325+
if self.as_root:
326+
service_args["user"] = "root"
307327
try:
308-
self.vm.run_service("qubes.StartApp+qubes-run-terminal")
328+
self.vm.run_service(
329+
"qubes.StartApp+qubes-run-terminal", **service_args
330+
)
309331
except exc.QubesException as ex:
310332
show_error(
311333
_("Error starting terminal"),
@@ -367,7 +389,9 @@ def __init__(self, vm, app, icon_cache):
367389
self.app = app
368390

369391
self.add(OpenFileManagerItem(self.vm, icon_cache))
370-
self.add(RunTerminalItem(self.vm, icon_cache))
392+
self.add(
393+
RunTerminalItem(self.vm, icon_cache, as_root=app.terminal_as_root)
394+
)
371395
self.add(PreferencesItem(self.vm, icon_cache))
372396
self.add(PauseItem(self.vm, icon_cache))
373397
self.add(ShutdownItem(self.vm, icon_cache))
@@ -563,6 +587,8 @@ def _set_submenu(self, state):
563587
submenu = PausedMenu(self.vm, self.icon_cache)
564588
else:
565589
submenu = DebugMenu(self.vm, self.icon_cache)
590+
submenu.connect("key-press-event", self.app.key_event)
591+
submenu.connect("key-release-event", self.app.key_event)
566592
# This is a workaround for a bug in Gtk which occurs when a
567593
# submenu is replaced while it is open.
568594
# see https://gitlab.gnome.org/GNOME/gtk/issues/885
@@ -651,6 +677,8 @@ def __init__(self, app_name, qapp, dispatcher, stats_dispatcher):
651677
self.tray_menu.set_reserve_toggle_size(False)
652678
self.fullscreen_window_hack = get_fullscreen_window_hack()
653679
self.fullscreen_window_hack.show_for_widget(self.tray_menu)
680+
self.tray_menu.connect("key-press-event", self.key_event)
681+
self.tray_menu.connect("key-release-event", self.key_event)
654682

655683
self.icon_cache = IconCache()
656684

@@ -718,6 +746,7 @@ def register_events(self):
718746
self.stats_dispatcher.add_handler("vm-stats", self.update_stats)
719747

720748
def show_menu(self, _unused, event):
749+
self.terminal_as_root = False
721750
self.tray_menu.popup_at_pointer(event) # None means current event
722751

723752
def emit_notification(self, vm, event, **kwargs):
@@ -1069,6 +1098,39 @@ def _disconnect_signals(self, _event):
10691098

10701099
self.stats_dispatcher.remove_handler("vm-stats", self.update_stats)
10711100

1101+
@property
1102+
def terminal_as_root(self):
1103+
try:
1104+
return self._terminal_as_root
1105+
except AttributeError:
1106+
self._terminal_as_root = False
1107+
return self.terminal_as_root
1108+
1109+
@terminal_as_root.setter
1110+
def terminal_as_root(self, as_root):
1111+
if as_root == self.terminal_as_root:
1112+
return
1113+
1114+
self._terminal_as_root = as_root
1115+
for item in self.menu_items.values():
1116+
if item.vm:
1117+
submenu = item.get_submenu()
1118+
if submenu is None:
1119+
continue
1120+
1121+
def do_emit(child):
1122+
if isinstance(child, RunTerminalItem):
1123+
child.set_as_root(as_root)
1124+
1125+
submenu.foreach(do_emit)
1126+
1127+
def key_event(self, _unused, event):
1128+
if event.keyval in [Gdk.KEY_Shift_L, Gdk.KEY_Shift_R]:
1129+
if event.type == Gdk.EventType.KEY_PRESS:
1130+
self.terminal_as_root = True
1131+
elif event.type == Gdk.EventType.KEY_RELEASE:
1132+
self.terminal_as_root = False
1133+
10721134

10731135
def main():
10741136
"""main function"""

0 commit comments

Comments
 (0)