Repository: qutebrowser/qutebrowser

Issue:
small window after exiting fullscreen mode
Reproduction:
1: make browser window fullscreen by doubleclicking on title bar in KDE
2: go to some youtube video
3: klick on fullscreen button to have the video all over the screen
4: press escape

Behaviour: window is not fullscreen anymore. It has the size it had before step 1
Expected: window should look exactly like after step 1

Bug is not just on youtube but on other pages having some fullcreen functionality as well.

qutebrowser v0.10.1
Git commit: v0.10.0-883-g629038632 (2017-07-03 10:29:28 +0200)
Backend: QtWebEngine (Chromium 56.0.2924.122)

CPython: 3.6.1
Qt: 5.9.0
PyQt: 5.8.2

sip: 4.19.2
colorama: no
pypeg2: 2.15
jinja2: 2.9.6
pygments: 2.2.0
yaml: 3.12
cssutils: no
typing: yes
OpenGL: 3.1.0
PyQt5.QtWebEngineWidgets: yes
PyQt5.QtWebKitWidgets: yes
pdf.js: no
SSL: OpenSSL 1.0.2l  25 May 2017

Style: Oxygen::Style
Platform: Linux-4.11.6-1-ARCH-x86_64-with-arch, 64bit
Linux distribution: Arch Linux (arch)
Frozen: False



Discussion:
FWIW, both qutebrowser and Chromium in i3 behave the same way.
My chromium behaves correctly.

Chromium	59.0.3071.109 (Developer Build) (64-bit)
Revision	9f4b44b898b326679817ee5a327256f8fac6ee75
OS	Linux
I see the same issue in Chromium 59.0.3071.115 and Chrome 61.0.3141.7 with herbstluftwm.

qutebrowser can probably do this properly though, by saving the fullscreen state when page fullscreen was requested, and restoring it (instead of just calling `showNormal`) when page fullscreen is left.
Which part is responsible for handling the exit-video-fullscreen request? It can't be done globally, because when there is a fullscreen video, I'd expect `<Esc>` to exit only the video fullscreen and restore the normal window fullscreen, but `<F11>` (or any window manager shortcut) to exit even the normal window fullscreen.
The flow is roughly like this:

- `QWebEnginePage.fullScreenRequested` (signal)
- `WebEngineTab._on_fullscreen_requested`
- `WebEngineTab.fullscreen_requested` (signal)
- `TabbedBrowser.cur_fullscreen_requested` (signal; if it was in the currently open tab)
- `MainWindow._on_fullscreen_requested`
- `self.showFullScreen()` / `self.showNormal()`

Fullscreen via the WM is handled by Qt only I think.

The `:fullscreen` command also calls `.showNormal()` or `.showFullScreen()`, or invokes a `QWebEnginePage.ExitFullScreen` `WebAction` when called with `--leave`.

So `MainWindow._on_fullscreen_requested` could simply store the state when fullscreen was requested and restore that.
> Fullscreen via the WM is handled by Qt only I think.

So if qutebrowser is in fullscreen due to the WM request, how can it know what to restore later on?
When the page requests fullscreen, the mainwindow can check `self.isFullScreen()`, save that, and when the page leaves fullscreen, not do anything rather than calling `self.showNormal()`.
Well, I still don't think that it's that easy. For example, if a tab is closed while a video is fullscreen, the global per-window state variable would have to be reset, otherwise the other tabs might get the wrong state later...
I think when a fullscreened tab is closed, we should do the equivalent of `:fullscreen --leave` (i.e. restore the fullscreen state) anyways - that's not the case currently, though, but would probably be a simple `self.action.exit_fullscreen()` in `WebEngineTab.shutdown`.
But we need to also tell the `MainWindow` to reset its `self._restore_fullscreen` variable or whatever, because it won't get the `fullscreen_requested(False)` signal when the tab is closed.
When we do `self.action.exit_fullscreen()` in `shutdown`, it does - it's exactly the same as pressing esc (or doing `:fullscreen --leave`).

Resolving patch:
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -38,7 +38,7 @@
                                  webelem, downloads)
 from qutebrowser.keyinput import modeman
 from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
-                               objreg, utils, typing)
+                               objreg, utils, typing, debug)
 from qutebrowser.utils.usertypes import KeyMode
 from qutebrowser.misc import editor, guiprocess
 from qutebrowser.completion.models import instances, sortfilter
@@ -2171,6 +2171,10 @@ def fullscreen(self, leave=False):
 
         window = self._tabbed_browser.window()
         if window.isFullScreen():
-            window.showNormal()
+            window.setWindowState(
+                window.state_before_fullscreen & ~Qt.WindowFullScreen)
         else:
+            window.state_before_fullscreen = window.windowState()
             window.showFullScreen()
+        log.misc.debug('state before fullscreen: {}'.format(
+            debug.qflags_key(Qt, window.state_before_fullscreen)))
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -615,6 +615,7 @@ def run_js_async(self, code, callback=None, *, world=None):
 
     def shutdown(self):
         self.shutting_down.emit()
+        self.action.exit_fullscreen()
         if qtutils.version_check('5.8', exact=True):
             # WORKAROUND for
             # https://bugreports.qt.io/browse/QTBUG-58563
diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -30,7 +30,8 @@
 
 from qutebrowser.commands import runners, cmdutils
 from qutebrowser.config import config
-from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
+from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
+                               debug)
 from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
 from qutebrowser.mainwindow.statusbar import bar
 from qutebrowser.completion import completionwidget, completer
@@ -123,6 +124,7 @@ class MainWindow(QWidget):
     Attributes:
         status: The StatusBar widget.
         tabbed_browser: The TabbedBrowser widget.
+        state_before_fullscreen: window state before activation of fullscreen.
         _downloadview: The DownloadView widget.
         _vbox: The main QVBoxLayout.
         _commandrunner: The main CommandRunner instance.
@@ -217,6 +219,8 @@ def __init__(self, *, private, geometry=None, parent=None):
 
         objreg.get("app").new_window.emit(self)
 
+        self.state_before_fullscreen = self.windowState()
+
     def _init_geometry(self, geometry):
         """Initialize the window geometry or load it from disk."""
         if geometry is not None:
@@ -483,9 +487,12 @@ def _connect_signals(self):
     @pyqtSlot(bool)
     def _on_fullscreen_requested(self, on):
         if on:
+            self.state_before_fullscreen = self.windowState()
             self.showFullScreen()
-        else:
-            self.showNormal()
+        elif self.isFullScreen():
+            self.setWindowState(self.state_before_fullscreen)
+        log.misc.debug('on: {}, state before fullscreen: {}'.format(
+            on, debug.qflags_key(Qt, self.state_before_fullscreen)))
 
     @cmdutils.register(instance='main-window', scope='window')
     @pyqtSlot()


Test patch: