From aafb874c8586f55b5161cd4287801a41fa1fe887 Mon Sep 17 00:00:00 2001 From: Cole Robinson Date: Wed, 9 Sep 2020 09:33:58 -0400 Subject: [PATCH] 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 --- tests/uitests/test_livetests.py | 45 ++++++++++++++++++++++--------- virtManager/details/console.py | 36 ++++++++++++------------- virtManager/details/viewers.py | 47 ++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 45 deletions(-) diff --git a/tests/uitests/test_livetests.py b/tests/uitests/test_livetests.py index f48d3eca3..beab22014 100644 --- a/tests/uitests/test_livetests.py +++ b/tests/uitests/test_livetests.py @@ -53,6 +53,15 @@ class Console(uiutils.UITestCase): conn = 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 # ############## @@ -134,8 +143,17 @@ class Console(uiutils.UITestCase): scalemenu.point() scalemenu.find("Only", "radio menu item").click() + # Check that modifiers don't work + win.click() + self.sleep(1) + win.keyCombo("w") + uiutils.check(lambda: win.showing) dom.destroy() win.find("Guest is not running.") + win.click_title() + self.sleep(1) + win.keyCombo("w") + uiutils.check(lambda: not win.showing) @_vm_wrapper("uitests-vnc-standard") def testConsoleVNCStandard(self, dom): @@ -172,10 +190,7 @@ class Console(uiutils.UITestCase): uiutils.check(lambda: con.showing) # Restart VM to retrigger console connect - smenu = win.find("Menu", "toggle button") - smenu.click() - smenu.find("Force Off", "menu item").click() - self._click_alert_button("you sure", "Yes") + self._destroy(win) win.find("Run", "push button").click() uiutils.check(lambda: passwd.showing) # Password should be filled in @@ -186,10 +201,7 @@ class Console(uiutils.UITestCase): uiutils.check(lambda: con.showing) # Restart VM to retrigger console connect - smenu = win.find("Menu", "toggle button") - smenu.click() - smenu.find("Force Off", "menu item").click() - self._click_alert_button("you sure", "Yes") + self._destroy(win) win.find("Run", "push button").click() uiutils.check(lambda: passwd.showing) # Password should be empty now @@ -267,10 +279,7 @@ class Console(uiutils.UITestCase): win.find("Details", "radio button").click() win.find("Console", "radio button").click() - smenu = win.find("Menu", "toggle button") - smenu.click() - smenu.find("Force Off", "menu item").click() - self._click_alert_button("you sure", "Yes") + self._destroy(win) view = self.app.root.find("^View$", "menu") view.click() # Triggers some tooltip cases @@ -286,6 +295,18 @@ class Console(uiutils.UITestCase): term = win.find("Serial Terminal") uiutils.check(lambda: term.showing) + # Ensure ctrl+w doesn't close the window, modifiers are disabled + term.click() + win.keyCombo("w") + uiutils.check(lambda: win.showing) + # Shut it down, ensure w works again + self._destroy(win) + win.click_title() + self.sleep(1) + win.keyCombo("w") + uiutils.check(lambda: not win.showing) + + @_vm_wrapper("uitests-spice-specific", opts=["--test-options=spice-agent", "--test-options=fake-console-resolution"]) diff --git a/virtManager/details/console.py b/virtManager/details/console.py index 3a1621e4c..25975ad8c 100644 --- a/virtManager/details/console.py +++ b/virtManager/details/console.py @@ -396,16 +396,6 @@ class vmmConsolePages(vmmGObjectUI): 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): if self._gtk_settings_accel is not None: return @@ -876,8 +866,18 @@ class vmmConsolePages(vmmGObjectUI): def _viewer_allocate_cb(self, src, ignore): self.widget("console-gfx-scroll").queue_resize() - def _viewer_focus_changed(self, ignore1=None, ignore2=None): - if self._someone_has_focus(): + def _viewer_keyboard_grab_cb(self, src): + 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() else: self._enable_modifiers() @@ -926,7 +926,7 @@ class vmmConsolePages(vmmGObjectUI): log.debug("Viewer disconnected") # Make sure modifiers are set correctly - self._viewer_focus_changed() + self._viewer_sync_modifiers() self._viewer_disconnected_set_page(errdetails, ssherr) self._refresh_resizeguest_from_settings() @@ -936,15 +936,15 @@ class vmmConsolePages(vmmGObjectUI): self._activate_viewer_page() # Make sure modifiers are set correctly - self._viewer_focus_changed() + self._viewer_sync_modifiers() def _connect_viewer_signals(self): self._viewer.connect("add-display-widget", self._viewer_add_display) self._viewer.connect("pointer-grab", self._pointer_grabbed) self._viewer.connect("pointer-ungrab", self._pointer_ungrabbed) self._viewer.connect("size-allocate", self._viewer_allocate_cb) - self._viewer.connect("focus-in-event", self._viewer_focus_changed) - self._viewer.connect("focus-out-event", self._viewer_focus_changed) + self._viewer.connect("keyboard-grab", self._viewer_keyboard_grab_cb) + self._viewer.connect("keyboard-ungrab", self._viewer_keyboard_grab_cb) self._viewer.connect("connected", self._viewer_connected) self._viewer.connect("disconnected", self._viewer_disconnected) self._viewer.connect("auth-error", self._viewer_auth_error) @@ -985,8 +985,8 @@ class vmmConsolePages(vmmGObjectUI): if not serial: serial = vmmSerialConsole(self.vm, target_port, name) - serial.set_focus_callbacks(self._viewer_focus_changed, - self._viewer_focus_changed) + serial.set_focus_callbacks(self._serial_focus_changed_cb, + self._serial_focus_changed_cb) title = Gtk.Label(label=name) self.widget("serial-pages").append_page(serial.get_box(), title) diff --git a/virtManager/details/viewers.py b/virtManager/details/viewers.py index 1a7c2c02b..b2623b275 100644 --- a/virtManager/details/viewers.py +++ b/virtManager/details/viewers.py @@ -36,10 +36,10 @@ class Viewer(vmmGObject): __gsignals__ = { "add-display-widget": (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-ungrab": (vmmGObject.RUN_FIRST, None, []), + "keyboard-grab": (vmmGObject.RUN_FIRST, None, []), + "keyboard-ungrab": (vmmGObject.RUN_FIRST, None, []), "connected": (vmmGObject.RUN_FIRST, None, []), "disconnected": (vmmGObject.RUN_FIRST, None, [str, str]), "auth-error": (vmmGObject.RUN_FIRST, None, [str, bool]), @@ -54,6 +54,7 @@ class Viewer(vmmGObject): self._vm = vm self._ginfo = ginfo self._tunnels = SSHTunnels(self._ginfo) + self._keyboard_grab = False self.add_gsettings_handle( self.config.on_keys_combination_changed(self._refresh_grab_keys)) @@ -90,10 +91,6 @@ class Viewer(vmmGObject): self._display.connect("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() def console_has_focus(self): 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): return self._set_size_request(*args, **kwargs) def console_size_allocate(self, *args, **kwargs): @@ -297,6 +296,8 @@ class VNCViewer(Viewer): self._make_signal_proxy("pointer-grab")) self._display.connect("vnc-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-failure", self._auth_failure_cb) @@ -306,6 +307,14 @@ class VNCViewer(Viewer): 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): self._tunnels.unlock() self.emit("connected") @@ -485,20 +494,28 @@ class SpiceViewer(Viewer): # Private helpers # ################### - def _init_widget(self): - 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): + def _mouse_grab_cb(self, src, grab): if grab: self.emit("pointer-grab") else: 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): self._spice_session = SpiceClientGLib.Session() SpiceClientGLib.set_session_option(self._spice_session)