viewers: Enable window modifiers when viewer doesn't have keyboard

Right now this is tied to widget focus, which is too strong. This
changes it so that say clicking on the window title or toolbar then
allows the user to use Alt+F to trigger the File menu for example.
This roughly matches how virt-viewer works

https://bugzilla.redhat.com/show_bug.cgi?id=1824480

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2020-09-09 09:33:58 -04:00
parent 33a61f7f30
commit aafb874c85
3 changed files with 83 additions and 45 deletions

View File

@ -53,6 +53,15 @@ class Console(uiutils.UITestCase):
conn = None conn = None
extraopts = None extraopts = None
def _destroy(self, win):
smenu = win.find("Menu", "toggle button")
smenu.click()
smenu.find("Force Off", "menu item").click()
self._click_alert_button("you sure", "Yes")
run = win.find("Run", "push button")
uiutils.check(lambda: run.sensitive)
############## ##############
# Test cases # # Test cases #
############## ##############
@ -134,8 +143,17 @@ class Console(uiutils.UITestCase):
scalemenu.point() scalemenu.point()
scalemenu.find("Only", "radio menu item").click() scalemenu.find("Only", "radio menu item").click()
# Check that modifiers don't work
win.click()
self.sleep(1)
win.keyCombo("<ctrl>w")
uiutils.check(lambda: win.showing)
dom.destroy() dom.destroy()
win.find("Guest is not running.") win.find("Guest is not running.")
win.click_title()
self.sleep(1)
win.keyCombo("<ctrl>w")
uiutils.check(lambda: not win.showing)
@_vm_wrapper("uitests-vnc-standard") @_vm_wrapper("uitests-vnc-standard")
def testConsoleVNCStandard(self, dom): def testConsoleVNCStandard(self, dom):
@ -172,10 +190,7 @@ class Console(uiutils.UITestCase):
uiutils.check(lambda: con.showing) uiutils.check(lambda: con.showing)
# Restart VM to retrigger console connect # Restart VM to retrigger console connect
smenu = win.find("Menu", "toggle button") self._destroy(win)
smenu.click()
smenu.find("Force Off", "menu item").click()
self._click_alert_button("you sure", "Yes")
win.find("Run", "push button").click() win.find("Run", "push button").click()
uiutils.check(lambda: passwd.showing) uiutils.check(lambda: passwd.showing)
# Password should be filled in # Password should be filled in
@ -186,10 +201,7 @@ class Console(uiutils.UITestCase):
uiutils.check(lambda: con.showing) uiutils.check(lambda: con.showing)
# Restart VM to retrigger console connect # Restart VM to retrigger console connect
smenu = win.find("Menu", "toggle button") self._destroy(win)
smenu.click()
smenu.find("Force Off", "menu item").click()
self._click_alert_button("you sure", "Yes")
win.find("Run", "push button").click() win.find("Run", "push button").click()
uiutils.check(lambda: passwd.showing) uiutils.check(lambda: passwd.showing)
# Password should be empty now # Password should be empty now
@ -267,10 +279,7 @@ class Console(uiutils.UITestCase):
win.find("Details", "radio button").click() win.find("Details", "radio button").click()
win.find("Console", "radio button").click() win.find("Console", "radio button").click()
smenu = win.find("Menu", "toggle button") self._destroy(win)
smenu.click()
smenu.find("Force Off", "menu item").click()
self._click_alert_button("you sure", "Yes")
view = self.app.root.find("^View$", "menu") view = self.app.root.find("^View$", "menu")
view.click() view.click()
# Triggers some tooltip cases # Triggers some tooltip cases
@ -286,6 +295,18 @@ class Console(uiutils.UITestCase):
term = win.find("Serial Terminal") term = win.find("Serial Terminal")
uiutils.check(lambda: term.showing) uiutils.check(lambda: term.showing)
# Ensure ctrl+w doesn't close the window, modifiers are disabled
term.click()
win.keyCombo("<ctrl>w")
uiutils.check(lambda: win.showing)
# Shut it down, ensure <ctrl>w works again
self._destroy(win)
win.click_title()
self.sleep(1)
win.keyCombo("<ctrl>w")
uiutils.check(lambda: not win.showing)
@_vm_wrapper("uitests-spice-specific", @_vm_wrapper("uitests-spice-specific",
opts=["--test-options=spice-agent", opts=["--test-options=spice-agent",
"--test-options=fake-console-resolution"]) "--test-options=fake-console-resolution"])

View File

@ -396,16 +396,6 @@ class vmmConsolePages(vmmGObjectUI):
self.topwin.set_title(title) self.topwin.set_title(title)
def _someone_has_focus(self):
if (self._viewer and
self._viewer.console_has_focus() and
self._viewer.console_is_open()):
return True
for serial in self._serial_consoles:
if serial.has_focus():
return True
def _disable_modifiers(self): def _disable_modifiers(self):
if self._gtk_settings_accel is not None: if self._gtk_settings_accel is not None:
return return
@ -876,8 +866,18 @@ class vmmConsolePages(vmmGObjectUI):
def _viewer_allocate_cb(self, src, ignore): def _viewer_allocate_cb(self, src, ignore):
self.widget("console-gfx-scroll").queue_resize() self.widget("console-gfx-scroll").queue_resize()
def _viewer_focus_changed(self, ignore1=None, ignore2=None): def _viewer_keyboard_grab_cb(self, src):
if self._someone_has_focus(): self._viewer_sync_modifiers()
def _serial_focus_changed_cb(self, src, event):
self._viewer_sync_modifiers()
def _viewer_sync_modifiers(self):
serial_has_focus = any([s.has_focus() for s in self._serial_consoles])
viewer_keyboard_grab = (self._viewer and
self._viewer.console_has_keyboard_grab())
if serial_has_focus or viewer_keyboard_grab:
self._disable_modifiers() self._disable_modifiers()
else: else:
self._enable_modifiers() self._enable_modifiers()
@ -926,7 +926,7 @@ class vmmConsolePages(vmmGObjectUI):
log.debug("Viewer disconnected") log.debug("Viewer disconnected")
# Make sure modifiers are set correctly # Make sure modifiers are set correctly
self._viewer_focus_changed() self._viewer_sync_modifiers()
self._viewer_disconnected_set_page(errdetails, ssherr) self._viewer_disconnected_set_page(errdetails, ssherr)
self._refresh_resizeguest_from_settings() self._refresh_resizeguest_from_settings()
@ -936,15 +936,15 @@ class vmmConsolePages(vmmGObjectUI):
self._activate_viewer_page() self._activate_viewer_page()
# Make sure modifiers are set correctly # Make sure modifiers are set correctly
self._viewer_focus_changed() self._viewer_sync_modifiers()
def _connect_viewer_signals(self): def _connect_viewer_signals(self):
self._viewer.connect("add-display-widget", self._viewer_add_display) self._viewer.connect("add-display-widget", self._viewer_add_display)
self._viewer.connect("pointer-grab", self._pointer_grabbed) self._viewer.connect("pointer-grab", self._pointer_grabbed)
self._viewer.connect("pointer-ungrab", self._pointer_ungrabbed) self._viewer.connect("pointer-ungrab", self._pointer_ungrabbed)
self._viewer.connect("size-allocate", self._viewer_allocate_cb) self._viewer.connect("size-allocate", self._viewer_allocate_cb)
self._viewer.connect("focus-in-event", self._viewer_focus_changed) self._viewer.connect("keyboard-grab", self._viewer_keyboard_grab_cb)
self._viewer.connect("focus-out-event", self._viewer_focus_changed) self._viewer.connect("keyboard-ungrab", self._viewer_keyboard_grab_cb)
self._viewer.connect("connected", self._viewer_connected) self._viewer.connect("connected", self._viewer_connected)
self._viewer.connect("disconnected", self._viewer_disconnected) self._viewer.connect("disconnected", self._viewer_disconnected)
self._viewer.connect("auth-error", self._viewer_auth_error) self._viewer.connect("auth-error", self._viewer_auth_error)
@ -985,8 +985,8 @@ class vmmConsolePages(vmmGObjectUI):
if not serial: if not serial:
serial = vmmSerialConsole(self.vm, target_port, name) serial = vmmSerialConsole(self.vm, target_port, name)
serial.set_focus_callbacks(self._viewer_focus_changed, serial.set_focus_callbacks(self._serial_focus_changed_cb,
self._viewer_focus_changed) self._serial_focus_changed_cb)
title = Gtk.Label(label=name) title = Gtk.Label(label=name)
self.widget("serial-pages").append_page(serial.get_box(), title) self.widget("serial-pages").append_page(serial.get_box(), title)

View File

@ -36,10 +36,10 @@ class Viewer(vmmGObject):
__gsignals__ = { __gsignals__ = {
"add-display-widget": (vmmGObject.RUN_FIRST, None, [object]), "add-display-widget": (vmmGObject.RUN_FIRST, None, [object]),
"size-allocate": (vmmGObject.RUN_FIRST, None, [object]), "size-allocate": (vmmGObject.RUN_FIRST, None, [object]),
"focus-in-event": (vmmGObject.RUN_FIRST, None, [object]),
"focus-out-event": (vmmGObject.RUN_FIRST, None, [object]),
"pointer-grab": (vmmGObject.RUN_FIRST, None, []), "pointer-grab": (vmmGObject.RUN_FIRST, None, []),
"pointer-ungrab": (vmmGObject.RUN_FIRST, None, []), "pointer-ungrab": (vmmGObject.RUN_FIRST, None, []),
"keyboard-grab": (vmmGObject.RUN_FIRST, None, []),
"keyboard-ungrab": (vmmGObject.RUN_FIRST, None, []),
"connected": (vmmGObject.RUN_FIRST, None, []), "connected": (vmmGObject.RUN_FIRST, None, []),
"disconnected": (vmmGObject.RUN_FIRST, None, [str, str]), "disconnected": (vmmGObject.RUN_FIRST, None, [str, str]),
"auth-error": (vmmGObject.RUN_FIRST, None, [str, bool]), "auth-error": (vmmGObject.RUN_FIRST, None, [str, bool]),
@ -54,6 +54,7 @@ class Viewer(vmmGObject):
self._vm = vm self._vm = vm
self._ginfo = ginfo self._ginfo = ginfo
self._tunnels = SSHTunnels(self._ginfo) self._tunnels = SSHTunnels(self._ginfo)
self._keyboard_grab = False
self.add_gsettings_handle( self.add_gsettings_handle(
self.config.on_keys_combination_changed(self._refresh_grab_keys)) self.config.on_keys_combination_changed(self._refresh_grab_keys))
@ -90,10 +91,6 @@ class Viewer(vmmGObject):
self._display.connect("size-allocate", self._display.connect("size-allocate",
self._make_signal_proxy("size-allocate")) self._make_signal_proxy("size-allocate"))
self._display.connect("focus-in-event",
self._make_signal_proxy("focus-in-event"))
self._display.connect("focus-out-event",
self._make_signal_proxy("focus-out-event"))
@ -210,6 +207,8 @@ class Viewer(vmmGObject):
return self._grab_focus() return self._grab_focus()
def console_has_focus(self): def console_has_focus(self):
return self._has_focus() return self._has_focus()
def console_has_keyboard_grab(self):
return bool(self._display and self._keyboard_grab)
def console_set_size_request(self, *args, **kwargs): def console_set_size_request(self, *args, **kwargs):
return self._set_size_request(*args, **kwargs) return self._set_size_request(*args, **kwargs)
def console_size_allocate(self, *args, **kwargs): def console_size_allocate(self, *args, **kwargs):
@ -297,6 +296,8 @@ class VNCViewer(Viewer):
self._make_signal_proxy("pointer-grab")) self._make_signal_proxy("pointer-grab"))
self._display.connect("vnc-pointer-ungrab", self._display.connect("vnc-pointer-ungrab",
self._make_signal_proxy("pointer-ungrab")) self._make_signal_proxy("pointer-ungrab"))
self._display.connect("vnc-keyboard-grab", self._keyboard_grab_cb)
self._display.connect("vnc-keyboard-ungrab", self._keyboard_ungrab_cb)
self._display.connect("vnc-auth-credential", self._auth_credential) self._display.connect("vnc-auth-credential", self._auth_credential)
self._display.connect("vnc-auth-failure", self._auth_failure_cb) self._display.connect("vnc-auth-failure", self._auth_failure_cb)
@ -306,6 +307,14 @@ class VNCViewer(Viewer):
self._display.show() self._display.show()
def _keyboard_grab_cb(self, src):
self._keyboard_grab = True
self.emit("keyboard-grab")
def _keyboard_ungrab_cb(self, src):
self._keyboard_grab = False
self.emit("keyboard-ungrab")
def _connected_cb(self, ignore): def _connected_cb(self, ignore):
self._tunnels.unlock() self._tunnels.unlock()
self.emit("connected") self.emit("connected")
@ -485,20 +494,28 @@ class SpiceViewer(Viewer):
# Private helpers # # Private helpers #
################### ###################
def _init_widget(self): def _mouse_grab_cb(self, src, grab):
self.emit("add-display-widget", self._display)
self._display.realize()
self._display.connect("mouse-grab", self._mouse_grab_event)
self._display.show()
def _mouse_grab_event(self, ignore, grab):
if grab: if grab:
self.emit("pointer-grab") self.emit("pointer-grab")
else: else:
self.emit("pointer-ungrab") self.emit("pointer-ungrab")
def _keyboard_grab_cb(self, src, grab):
self._keyboard_grab = grab
if grab:
self.emit("keyboard-grab")
else:
self.emit("keyboard-ungrab")
def _init_widget(self):
self.emit("add-display-widget", self._display)
self._display.realize()
self._display.connect("mouse-grab", self._mouse_grab_cb)
self._display.connect("keyboard-grab", self._keyboard_grab_cb)
self._display.show()
def _create_spice_session(self): def _create_spice_session(self):
self._spice_session = SpiceClientGLib.Session() self._spice_session = SpiceClientGLib.Session()
SpiceClientGLib.set_session_option(self._spice_session) SpiceClientGLib.set_session_option(self._spice_session)