Add fullscreen support for QtWebEngine

This commit is contained in:
Florian Bruhin 2017-01-02 20:02:02 +01:00
parent 1209724f83
commit 98e6ccf548
14 changed files with 133 additions and 24 deletions

View File

@ -27,6 +27,7 @@ Added
- Proxy support for QtWebEngine with Qt >= 5.8
- Support for the `content -> cookies-store` option with QtWebEngine
- Support for the `storage -> cache-size` option with QtWebEngine
- Support for the HTML5 fullscreen API (e.g. youtube videos) with QtWebEngine
Changed
~~~~~~~

View File

@ -319,8 +319,13 @@ How many pages to go forward.
[[fullscreen]]
=== fullscreen
Syntax: +:fullscreen [*--leave*]+
Toggle fullscreen mode.
==== optional arguments
* +*-l*+, +*--leave*+: Only leave fullscreen if it was entered by the page.
[[help]]
=== help
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+

View File

@ -94,6 +94,18 @@ class TabData:
self.override_target = None
class AbstractAction:
"""Attribute of AbstractTab for Qt WebActions."""
def __init__(self):
self._widget = None
def exit_fullscreen(self):
"""Exit the fullscreen mode."""
raise NotImplementedError
class AbstractPrinting:
"""Attribute of AbstractTab for printing the page."""
@ -513,6 +525,9 @@ class AbstractTab(QWidget):
new_tab_requested: Emitted when a new tab should be opened with the
given URL.
load_status_changed: The loading status changed
fullscreen_requested: Fullscreen display was requested by the page.
arg: True if fullscreen should be turned on,
False if it should be turned off.
"""
window_close_requested = pyqtSignal()
@ -528,6 +543,7 @@ class AbstractTab(QWidget):
shutting_down = pyqtSignal()
contents_size_changed = pyqtSignal(QSizeF)
add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title
fullscreen_requested = pyqtSignal(bool)
def __init__(self, win_id, mode_manager, parent=None):
self.win_id = win_id
@ -548,6 +564,7 @@ class AbstractTab(QWidget):
# self.search = AbstractSearch(parent=self)
# self.printing = AbstractPrinting()
# self.elements = AbstractElements(self)
# self.action = AbstractAction()
self.data = TabData()
self._layout = miscwidgets.WrapperLayout(self)
@ -576,6 +593,7 @@ class AbstractTab(QWidget):
self.zoom._widget = widget
self.search._widget = widget
self.printing._widget = widget
self.action._widget = widget
self.elements._widget = widget
self._install_event_filter()

View File

@ -2069,3 +2069,24 @@ class CommandDispatcher:
"""
if bg or tab or window or url != old_url:
self.openurl(url=url, bg=bg, tab=tab, window=window)
@cmdutils.register(instance='command-dispatcher', scope='window')
def fullscreen(self, leave=False):
"""Toggle fullscreen mode.
Args:
leave: Only leave fullscreen if it was entered by the page.
"""
if leave:
tab = self._current_widget()
try:
tab.action.exit_fullscreen()
except browsertab.UnsupportedOperationError:
pass
return
window = self._tabbed_browser.window()
if window.isFullScreen():
window.showNormal()
else:
window.showFullScreen()

View File

@ -164,6 +164,8 @@ def init(args):
# https://bugreports.qt.io/browse/QTBUG-58650
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
websettings.init_mappings(MAPPINGS)
objreg.get('config').changed.connect(update_settings)

View File

@ -79,6 +79,17 @@ _JS_WORLD_MAP = {
}
class WebEngineAction(browsertab.AbstractAction):
"""QtWebKit implementations related to web actions."""
def _action(self, action):
self._widget.triggerPageAction(action)
def exit_fullscreen(self):
self._action(QWebEnginePage.ExitFullScreen)
class WebEnginePrinting(browsertab.AbstractPrinting):
"""QtWebEngine implementations related to printing."""
@ -473,6 +484,7 @@ class WebEngineTab(browsertab.AbstractTab):
self.search = WebEngineSearch(parent=self)
self.printing = WebEnginePrinting()
self.elements = WebEngineElements(self)
self.action = WebEngineAction()
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
@ -640,6 +652,12 @@ class WebEngineTab(browsertab.AbstractTab):
url=url_string, error="Authentication required", icon='')
self.set_html(error_page)
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
# FIXME:qtwebengine do we want a setting to disallow this?
request.accept()
self.fullscreen_requested.emit(request.toggleOn())
def _connect_signals(self):
view = self._widget
page = view.page()
@ -653,6 +671,7 @@ class WebEngineTab(browsertab.AbstractTab):
page.loadFinished.connect(self._on_load_finished)
page.certificate_error.connect(self._on_ssl_errors)
page.authenticationRequired.connect(self._on_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested)
view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed)

View File

@ -52,6 +52,14 @@ def init():
objreg.register('js-bridge', js_bridge)
class WebKitAction(browsertab.AbstractAction):
"""QtWebKit implementations related to web actions."""
def exit_fullscreen(self):
raise browsertab.UnsupportedOperationError
class WebKitPrinting(browsertab.AbstractPrinting):
"""QtWebKit implementations related to printing."""
@ -610,6 +618,7 @@ class WebKitTab(browsertab.AbstractTab):
self.search = WebKitSearch(parent=self)
self.printing = WebKitPrinting()
self.elements = WebKitElements(self)
self.action = WebKitAction()
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebKit

View File

@ -1549,7 +1549,8 @@ KEY_DATA = collections.OrderedDict([
])),
('normal', collections.OrderedDict([
('clear-keychain ;; search', ['<Escape>', '<Ctrl-[>']),
('clear-keychain ;; search ;; fullscreen --leave',
['<Escape>', '<Ctrl-[>']),
('set-cmd-text -s :open', ['o']),
('set-cmd-text :open {url:pretty}', ['go']),
('set-cmd-text -s :open -t', ['O']),
@ -1769,8 +1770,12 @@ CHANGED_KEY_COMMANDS = [
(re.compile(r'^download-page$'), r'download'),
(re.compile(r'^cancel-download$'), r'download-cancel'),
(re.compile(r"""^search (''|"")$"""), r'clear-keychain ;; search'),
(re.compile(r'^search$'), r'clear-keychain ;; search'),
(re.compile(r"""^search (''|"")$"""),
r'clear-keychain ;; search ;; fullscreen --leave'),
(re.compile(r'^search$'),
r'clear-keychain ;; search ;; fullscreen --leave'),
(re.compile(r'^clear-keychain ;; search$'),
r'clear-keychain ;; search ;; fullscreen --leave'),
(re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'),
(re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'),
@ -1784,7 +1789,8 @@ CHANGED_KEY_COMMANDS = [
(re.compile(r'^scroll 50 0$'), r'scroll right'),
(re.compile(r'^scroll ([-\d]+ [-\d]+)$'), r'scroll-px \1'),
(re.compile(r'^search *;; *clear-keychain$'), r'clear-keychain ;; search'),
(re.compile(r'^search *;; *clear-keychain$'),
r'clear-keychain ;; search ;; fullscreen --leave'),
(re.compile(r'^clear-keychain *;; *leave-mode$'), r'leave-mode'),
(re.compile(r'^download-remove --all$'), r'download-clear'),

View File

@ -456,6 +456,10 @@ class MainWindow(QWidget):
tabs.cur_url_changed.connect(status.url.set_url)
tabs.cur_link_hovered.connect(status.url.set_hover_url)
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
tabs.page_fullscreen_requested.connect(
self._on_page_fullscreen_requested)
tabs.page_fullscreen_requested.connect(
status.on_page_fullscreen_requested)
# command input / completion
mode_manager.left.connect(tabs.on_mode_left)
@ -463,6 +467,13 @@ class MainWindow(QWidget):
completion_obj.on_clear_completion_selection)
cmd.hide_completion.connect(completion_obj.hide)
@pyqtSlot(bool)
def _on_page_fullscreen_requested(self, on):
if on:
self.showFullScreen()
else:
self.showNormal()
@cmdutils.register(instance='main-window', scope='window')
@pyqtSlot()
def close(self):
@ -474,14 +485,6 @@ class MainWindow(QWidget):
"""
super().close()
@cmdutils.register(instance='main-window', scope='window')
def fullscreen(self):
"""Toggle fullscreen mode."""
if self.isFullScreen():
self.showNormal()
else:
self.showFullScreen()
def resizeEvent(self, e):
"""Extend resizewindow's resizeEvent to adjust completion.

View File

@ -46,6 +46,8 @@ class StatusBar(QWidget):
_hbox: The main QHBoxLayout.
_stack: The QStackedLayout with cmd/txt widgets.
_win_id: The window ID the statusbar is associated with.
_page_fullscreen: Whether the webpage (e.g. a video) is shown
fullscreen.
Class attributes:
_prompt_active: If we're currently in prompt-mode.
@ -143,6 +145,7 @@ class StatusBar(QWidget):
self._win_id = win_id
self._option = None
self._page_fullscreen = False
self._hbox = QHBoxLayout(self)
self.set_hbox_padding()
@ -193,7 +196,7 @@ class StatusBar(QWidget):
def maybe_hide(self):
"""Hide the statusbar if it's configured to do so."""
hide = config.get('ui', 'hide-statusbar')
if hide:
if hide or self._page_fullscreen:
self.hide()
else:
self.show()
@ -306,6 +309,11 @@ class StatusBar(QWidget):
usertypes.KeyMode.yesno]:
self.set_mode_active(old_mode, False)
@pyqtSlot(bool)
def on_page_fullscreen_requested(self, on):
self._page_fullscreen = on
self.maybe_hide()
def resizeEvent(self, e):
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.

View File

@ -98,6 +98,7 @@ class TabbedBrowser(tabwidget.TabWidget):
resized = pyqtSignal('QRect')
current_tab_changed = pyqtSignal(browsertab.AbstractTab)
new_tab = pyqtSignal(browsertab.AbstractTab, int)
page_fullscreen_requested = pyqtSignal(bool)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
@ -199,6 +200,9 @@ class TabbedBrowser(tabwidget.TabWidget):
functools.partial(self.on_window_close_requested, tab))
tab.new_tab_requested.connect(self.tabopen)
tab.add_history_item.connect(objreg.get('web-history').add_from_tab)
tab.fullscreen_requested.connect(self.page_fullscreen_requested)
tab.fullscreen_requested.connect(
self.tabBar().on_page_fullscreen_requested)
def current_url(self):
"""Get the URL of the current tab.

View File

@ -259,6 +259,8 @@ class TabBar(QTabBar):
Attributes:
vertical: When the tab bar is currently vertical.
win_id: The window ID this TabBar belongs to.
_page_fullscreen: Whether the webpage (e.g. a video) is shown
fullscreen.
"""
def __init__(self, win_id, parent=None):
@ -269,6 +271,7 @@ class TabBar(QTabBar):
config_obj = objreg.get('config')
config_obj.changed.connect(self.set_font)
self.vertical = False
self._page_fullscreen = False
self._auto_hide_timer = QTimer()
self._auto_hide_timer.setSingleShot(True)
self._auto_hide_timer.setInterval(
@ -296,20 +299,24 @@ class TabBar(QTabBar):
self._auto_hide_timer.setInterval(
config.get('tabs', 'show-switching-delay'))
@pyqtSlot(bool)
def on_page_fullscreen_requested(self, on):
self._page_fullscreen = on
self._tabhide()
def on_change(self):
"""Show tab bar when current tab got changed."""
show = config.get('tabs', 'show')
if show == 'switching':
if show == 'switching' or self._page_fullscreen:
self.show()
self._auto_hide_timer.start()
def _tabhide(self):
"""Hide the tab bar if needed."""
show = config.get('tabs', 'show')
show_never = show == 'never'
switching = show == 'switching'
multiple = show == 'multiple'
if show_never or (multiple and self.count() == 1) or switching:
if (show in ['never', 'switching'] or
(show == 'multiple' and self.count() == 1) or
self._page_fullscreen):
self.hide()
else:
self.show()

View File

@ -110,6 +110,7 @@ class Tab(browsertab.AbstractTab):
self.search = browsertab.AbstractSearch(parent=self)
self.printing = browsertab.AbstractPrinting()
self.elements = browsertab.AbstractElements(self)
self.action = browsertab.AbstractAction()
def _install_event_filter(self):
pass

View File

@ -281,9 +281,9 @@ class TestKeyConfigParser:
('download-page', 'download'),
('cancel-download', 'download-cancel'),
('search ""', 'clear-keychain ;; search'),
("search ''", 'clear-keychain ;; search'),
("search", 'clear-keychain ;; search'),
('search ""', 'clear-keychain ;; search ;; fullscreen --leave'),
("search ''", 'clear-keychain ;; search ;; fullscreen --leave'),
("search", 'clear-keychain ;; search ;; fullscreen --leave'),
("search ;; foobar", None),
('search "foo"', None),
@ -305,11 +305,16 @@ class TestKeyConfigParser:
('scroll 0 0', 'scroll-px 0 0'),
('scroll 23 42', 'scroll-px 23 42'),
('search ;; clear-keychain', 'clear-keychain ;; search'),
('search;;clear-keychain', 'clear-keychain ;; search'),
('search ;; clear-keychain',
'clear-keychain ;; search ;; fullscreen --leave'),
('search;;clear-keychain',
'clear-keychain ;; search ;; fullscreen --leave'),
('search;;foo', None),
('clear-keychain ;; leave-mode', 'leave-mode'),
('clear-keychain ;; search',
'clear-keychain ;; search ;; fullscreen --leave'),
('leave-mode ;; foo', None),
('search ;; clear-keychain',
'clear-keychain ;; search ;; fullscreen --leave'),
('download-remove --all', 'download-clear'),