mirror of https://gitlab.com/openlp/openlp.git
Display Custom Scheme
This commit is contained in:
parent
9e34f9eca1
commit
69d20d8fa2
|
@ -23,166 +23,11 @@ import os
|
|||
|
||||
from flask import Blueprint, send_from_directory
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.mime import get_mime_type
|
||||
|
||||
main_views = Blueprint('main', __name__)
|
||||
|
||||
|
||||
def get_mime_type(file):
|
||||
if file.lower().endswith('.aac'):
|
||||
mime_type = 'audio/aac'
|
||||
elif file.lower().endswith('.abw'):
|
||||
mime_type = 'application/x-abiword'
|
||||
elif file.lower().endswith('.arc'):
|
||||
mime_type = 'application/x-freearc'
|
||||
elif file.lower().endswith('.avi'):
|
||||
mime_type = 'video/x-msvideo'
|
||||
elif file.lower().endswith('.azw'):
|
||||
mime_type = 'application/vnd.amazon.ebook'
|
||||
elif file.lower().endswith('.bin'):
|
||||
mime_type = 'application/octet-stream'
|
||||
elif file.lower().endswith('.bmp'):
|
||||
mime_type = 'image/bmp'
|
||||
elif file.lower().endswith('.bz'):
|
||||
mime_type = 'application/x-bzip'
|
||||
elif file.lower().endswith('.bz2'):
|
||||
mime_type = 'application/x-bzip2'
|
||||
elif file.lower().endswith('.cda'):
|
||||
mime_type = 'application/x-cdf'
|
||||
elif file.lower().endswith('.csh'):
|
||||
mime_type = 'application/x-csh'
|
||||
elif file.lower().endswith('.css'):
|
||||
mime_type = 'text/css'
|
||||
elif file.lower().endswith('.csv'):
|
||||
mime_type = 'text/csv'
|
||||
elif file.lower().endswith('.doc'):
|
||||
mime_type = 'application/msword'
|
||||
elif file.lower().endswith('.docx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
elif file.lower().endswith('.eot'):
|
||||
mime_type = 'application/vnd.ms-fontobject'
|
||||
elif file.lower().endswith('.epub'):
|
||||
mime_type = 'application/epub+zip'
|
||||
elif file.lower().endswith('.gz'):
|
||||
mime_type = 'application/gzip'
|
||||
elif file.lower().endswith('.gif'):
|
||||
mime_type = 'image/gif'
|
||||
elif file.lower().endswith('.htm'):
|
||||
mime_type = 'text/html'
|
||||
elif file.lower().endswith('.html'):
|
||||
mime_type = 'text/html'
|
||||
elif file.lower().endswith('.ico'):
|
||||
mime_type = 'image/vnd.microsoft.icon'
|
||||
elif file.lower().endswith('.ics'):
|
||||
mime_type = 'text/calendar'
|
||||
elif file.lower().endswith('.jar'):
|
||||
mime_type = 'application/java-archive'
|
||||
elif file.lower().endswith('.jpeg'):
|
||||
mime_type = 'image/jpeg'
|
||||
elif file.lower().endswith('.jpg'):
|
||||
mime_type = 'image/jpeg'
|
||||
elif file.lower().endswith('.js'):
|
||||
mime_type = 'application/javascript'
|
||||
elif file.lower().endswith('.json'):
|
||||
mime_type = 'application/json'
|
||||
elif file.lower().endswith('.jsonld'):
|
||||
mime_type = 'application/ld+json'
|
||||
elif file.lower().endswith('.mid'):
|
||||
mime_type = 'audio/midi'
|
||||
elif file.lower().endswith('.midi'):
|
||||
mime_type = 'audio/x-midi'
|
||||
elif file.lower().endswith('.mjs'):
|
||||
mime_type = 'text/javascript'
|
||||
elif file.lower().endswith('.mp3'):
|
||||
mime_type = 'audio/mpeg'
|
||||
elif file.lower().endswith('.mp4'):
|
||||
mime_type = 'video/mp4'
|
||||
elif file.lower().endswith('.mpeg'):
|
||||
mime_type = 'video/mpeg'
|
||||
elif file.lower().endswith('.mpkg'):
|
||||
mime_type = 'application/vnd.apple.installer+xml'
|
||||
elif file.lower().endswith('.odp'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.presentation'
|
||||
elif file.lower().endswith('.ods'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
elif file.lower().endswith('.odt'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.text'
|
||||
elif file.lower().endswith('.oga'):
|
||||
mime_type = 'audio/ogg'
|
||||
elif file.lower().endswith('.ogv'):
|
||||
mime_type = 'video/ogg'
|
||||
elif file.lower().endswith('.ogx'):
|
||||
mime_type = 'application/ogg'
|
||||
elif file.lower().endswith('.opus'):
|
||||
mime_type = 'audio/opus'
|
||||
elif file.lower().endswith('.otf'):
|
||||
mime_type = 'application/x-font-opentype'
|
||||
elif file.lower().endswith('.png'):
|
||||
mime_type = 'image/png'
|
||||
elif file.lower().endswith('.pdf'):
|
||||
mime_type = 'application/pdf'
|
||||
elif file.lower().endswith('.php'):
|
||||
mime_type = 'application/x-httpd-php'
|
||||
elif file.lower().endswith('.ppt'):
|
||||
mime_type = 'application/vnd.ms-powerpoint'
|
||||
elif file.lower().endswith('.pptx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
elif file.lower().endswith('.rar'):
|
||||
mime_type = 'application/vnd.rar'
|
||||
elif file.lower().endswith('.rtf'):
|
||||
mime_type = 'application/rtf'
|
||||
elif file.lower().endswith('.sfnt'):
|
||||
mime_type = 'application/font-sfnt'
|
||||
elif file.lower().endswith('.sh'):
|
||||
mime_type = 'application/x-sh'
|
||||
elif file.lower().endswith('.svg'):
|
||||
mime_type = 'image/svg+xml'
|
||||
elif file.lower().endswith('.swf'):
|
||||
mime_type = 'application/x-shockwave-flash'
|
||||
elif file.lower().endswith('.tar'):
|
||||
mime_type = 'application/x-tar'
|
||||
elif file.lower().endswith('.tif'):
|
||||
mime_type = 'image/tiff'
|
||||
elif file.lower().endswith('.tiff'):
|
||||
mime_type = 'image/tiff'
|
||||
elif file.lower().endswith('.ts'):
|
||||
mime_type = 'video/mp2t'
|
||||
elif file.lower().endswith('.ttf'):
|
||||
mime_type = 'application/x-font-ttf'
|
||||
elif file.lower().endswith('.txt'):
|
||||
mime_type = 'text/plain'
|
||||
elif file.lower().endswith('.vsd'):
|
||||
mime_type = 'application/vnd.visio'
|
||||
elif file.lower().endswith('.wav'):
|
||||
mime_type = 'audio/wav'
|
||||
elif file.lower().endswith('.weba'):
|
||||
mime_type = 'audio/webm'
|
||||
elif file.lower().endswith('.webm'):
|
||||
mime_type = 'video/webm'
|
||||
elif file.lower().endswith('.webp'):
|
||||
mime_type = 'image/webp'
|
||||
elif file.lower().endswith('.woff'):
|
||||
mime_type = 'application/font-woff'
|
||||
elif file.lower().endswith('.woff2'):
|
||||
mime_type = 'application/font-woff2'
|
||||
elif file.lower().endswith('.xhtml'):
|
||||
mime_type = 'application/xhtml+xml'
|
||||
elif file.lower().endswith('.xls'):
|
||||
mime_type = 'application/vnd.ms-excel'
|
||||
elif file.lower().endswith('.xlsx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
elif file.lower().endswith('.xml'):
|
||||
mime_type = 'application/xml'
|
||||
elif file.lower().endswith('.xul'):
|
||||
mime_type = 'application/vnd.mozilla.xul+xml'
|
||||
elif file.lower().endswith('.zip'):
|
||||
mime_type = 'application/zip'
|
||||
elif file.lower().endswith('.7z'):
|
||||
mime_type = 'application/x-7z-compressed'
|
||||
else:
|
||||
mime_type = 'application/octet-stream'
|
||||
return mime_type
|
||||
|
||||
|
||||
@main_views.route('/', defaults={'path': ''})
|
||||
@main_views.route('/<path>')
|
||||
def index(path):
|
||||
|
|
|
@ -46,6 +46,7 @@ from openlp.core.common.platform import is_macosx, is_win
|
|||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.display.webengine import init_webview_custom_schemes
|
||||
from openlp.core.loader import loader
|
||||
from openlp.core.resources import qInitResources
|
||||
from openlp.core.server import Server
|
||||
|
@ -414,6 +415,7 @@ def main():
|
|||
qInitResources()
|
||||
# Now create and actually run the application.
|
||||
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
|
||||
init_webview_custom_schemes()
|
||||
application = QtWidgets.QApplication(qt_args)
|
||||
application.setOrganizationName('OpenLP')
|
||||
application.setOrganizationDomain('openlp.org')
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2023 OpenLP Developers #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
|
||||
def get_mime_type(file):
|
||||
if file.lower().endswith('.aac'):
|
||||
mime_type = 'audio/aac'
|
||||
elif file.lower().endswith('.abw'):
|
||||
mime_type = 'application/x-abiword'
|
||||
elif file.lower().endswith('.arc'):
|
||||
mime_type = 'application/x-freearc'
|
||||
elif file.lower().endswith('.avi'):
|
||||
mime_type = 'video/x-msvideo'
|
||||
elif file.lower().endswith('.azw'):
|
||||
mime_type = 'application/vnd.amazon.ebook'
|
||||
elif file.lower().endswith('.bin'):
|
||||
mime_type = 'application/octet-stream'
|
||||
elif file.lower().endswith('.bmp'):
|
||||
mime_type = 'image/bmp'
|
||||
elif file.lower().endswith('.bz'):
|
||||
mime_type = 'application/x-bzip'
|
||||
elif file.lower().endswith('.bz2'):
|
||||
mime_type = 'application/x-bzip2'
|
||||
elif file.lower().endswith('.cda'):
|
||||
mime_type = 'application/x-cdf'
|
||||
elif file.lower().endswith('.csh'):
|
||||
mime_type = 'application/x-csh'
|
||||
elif file.lower().endswith('.css'):
|
||||
mime_type = 'text/css'
|
||||
elif file.lower().endswith('.csv'):
|
||||
mime_type = 'text/csv'
|
||||
elif file.lower().endswith('.doc'):
|
||||
mime_type = 'application/msword'
|
||||
elif file.lower().endswith('.docx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
elif file.lower().endswith('.eot'):
|
||||
mime_type = 'application/vnd.ms-fontobject'
|
||||
elif file.lower().endswith('.epub'):
|
||||
mime_type = 'application/epub+zip'
|
||||
elif file.lower().endswith('.gz'):
|
||||
mime_type = 'application/gzip'
|
||||
elif file.lower().endswith('.gif'):
|
||||
mime_type = 'image/gif'
|
||||
elif file.lower().endswith('.htm'):
|
||||
mime_type = 'text/html'
|
||||
elif file.lower().endswith('.html'):
|
||||
mime_type = 'text/html'
|
||||
elif file.lower().endswith('.ico'):
|
||||
mime_type = 'image/vnd.microsoft.icon'
|
||||
elif file.lower().endswith('.ics'):
|
||||
mime_type = 'text/calendar'
|
||||
elif file.lower().endswith('.jar'):
|
||||
mime_type = 'application/java-archive'
|
||||
elif file.lower().endswith('.jpeg'):
|
||||
mime_type = 'image/jpeg'
|
||||
elif file.lower().endswith('.jpg'):
|
||||
mime_type = 'image/jpeg'
|
||||
elif file.lower().endswith('.js'):
|
||||
mime_type = 'application/javascript'
|
||||
elif file.lower().endswith('.json'):
|
||||
mime_type = 'application/json'
|
||||
elif file.lower().endswith('.jsonld'):
|
||||
mime_type = 'application/ld+json'
|
||||
elif file.lower().endswith('.mid'):
|
||||
mime_type = 'audio/midi'
|
||||
elif file.lower().endswith('.midi'):
|
||||
mime_type = 'audio/x-midi'
|
||||
elif file.lower().endswith('.mjs'):
|
||||
mime_type = 'text/javascript'
|
||||
elif file.lower().endswith('.mp3'):
|
||||
mime_type = 'audio/mpeg'
|
||||
elif file.lower().endswith('.mp4'):
|
||||
mime_type = 'video/mp4'
|
||||
elif file.lower().endswith('.mpeg'):
|
||||
mime_type = 'video/mpeg'
|
||||
elif file.lower().endswith('.mpkg'):
|
||||
mime_type = 'application/vnd.apple.installer+xml'
|
||||
elif file.lower().endswith('.odp'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.presentation'
|
||||
elif file.lower().endswith('.ods'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
elif file.lower().endswith('.odt'):
|
||||
mime_type = 'application/vnd.oasis.opendocument.text'
|
||||
elif file.lower().endswith('.oga'):
|
||||
mime_type = 'audio/ogg'
|
||||
elif file.lower().endswith('.ogv'):
|
||||
mime_type = 'video/ogg'
|
||||
elif file.lower().endswith('.ogx'):
|
||||
mime_type = 'application/ogg'
|
||||
elif file.lower().endswith('.opus'):
|
||||
mime_type = 'audio/opus'
|
||||
elif file.lower().endswith('.otf'):
|
||||
mime_type = 'application/x-font-opentype'
|
||||
elif file.lower().endswith('.png'):
|
||||
mime_type = 'image/png'
|
||||
elif file.lower().endswith('.pdf'):
|
||||
mime_type = 'application/pdf'
|
||||
elif file.lower().endswith('.php'):
|
||||
mime_type = 'application/x-httpd-php'
|
||||
elif file.lower().endswith('.ppt'):
|
||||
mime_type = 'application/vnd.ms-powerpoint'
|
||||
elif file.lower().endswith('.pptx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||
elif file.lower().endswith('.rar'):
|
||||
mime_type = 'application/vnd.rar'
|
||||
elif file.lower().endswith('.rtf'):
|
||||
mime_type = 'application/rtf'
|
||||
elif file.lower().endswith('.sfnt'):
|
||||
mime_type = 'application/font-sfnt'
|
||||
elif file.lower().endswith('.sh'):
|
||||
mime_type = 'application/x-sh'
|
||||
elif file.lower().endswith('.svg'):
|
||||
mime_type = 'image/svg+xml'
|
||||
elif file.lower().endswith('.swf'):
|
||||
mime_type = 'application/x-shockwave-flash'
|
||||
elif file.lower().endswith('.tar'):
|
||||
mime_type = 'application/x-tar'
|
||||
elif file.lower().endswith('.tif'):
|
||||
mime_type = 'image/tiff'
|
||||
elif file.lower().endswith('.tiff'):
|
||||
mime_type = 'image/tiff'
|
||||
elif file.lower().endswith('.ts'):
|
||||
mime_type = 'video/mp2t'
|
||||
elif file.lower().endswith('.ttf'):
|
||||
mime_type = 'application/x-font-ttf'
|
||||
elif file.lower().endswith('.txt'):
|
||||
mime_type = 'text/plain'
|
||||
elif file.lower().endswith('.vsd'):
|
||||
mime_type = 'application/vnd.visio'
|
||||
elif file.lower().endswith('.wav'):
|
||||
mime_type = 'audio/wav'
|
||||
elif file.lower().endswith('.weba'):
|
||||
mime_type = 'audio/webm'
|
||||
elif file.lower().endswith('.webm'):
|
||||
mime_type = 'video/webm'
|
||||
elif file.lower().endswith('.webp'):
|
||||
mime_type = 'image/webp'
|
||||
elif file.lower().endswith('.woff'):
|
||||
mime_type = 'application/font-woff'
|
||||
elif file.lower().endswith('.woff2'):
|
||||
mime_type = 'application/font-woff2'
|
||||
elif file.lower().endswith('.xhtml'):
|
||||
mime_type = 'application/xhtml+xml'
|
||||
elif file.lower().endswith('.xls'):
|
||||
mime_type = 'application/vnd.ms-excel'
|
||||
elif file.lower().endswith('.xlsx'):
|
||||
mime_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
elif file.lower().endswith('.xml'):
|
||||
mime_type = 'application/xml'
|
||||
elif file.lower().endswith('.xul'):
|
||||
mime_type = 'application/vnd.mozilla.xul+xml'
|
||||
elif file.lower().endswith('.zip'):
|
||||
mime_type = 'application/zip'
|
||||
elif file.lower().endswith('.7z'):
|
||||
mime_type = 'application/x-7z-compressed'
|
||||
else:
|
||||
mime_type = 'application/octet-stream'
|
||||
return mime_type
|
|
@ -456,7 +456,7 @@ var Display = {
|
|||
section.setAttribute("data-background", bg_color);
|
||||
section.setAttribute("style", "height: 100%; width: 100%;");
|
||||
var img = document.createElement('img');
|
||||
img.src = image;
|
||||
img.src = Display._getFileUrl(image);
|
||||
img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%");
|
||||
section.appendChild(img);
|
||||
Display._slides['0'] = 0;
|
||||
|
@ -734,7 +734,7 @@ var Display = {
|
|||
section.setAttribute("id", index);
|
||||
section.setAttribute("style", "height: 100%; width: 100%;");
|
||||
var img = document.createElement('img');
|
||||
img.src = slide.path;
|
||||
img.src = Display._getFileUrl(slide.path);
|
||||
img.setAttribute("style", "width: 100%; height: 100%; margin: 0; object-fit: contain;");
|
||||
img.setAttribute('data-slide', index);
|
||||
section.appendChild(img);
|
||||
|
@ -1211,12 +1211,12 @@ var Display = {
|
|||
}
|
||||
break;
|
||||
case BackgroundType.Image:
|
||||
backgroundContent = "url('" + Display._theme.background_filename + "')";
|
||||
backgroundContent = "url('" + Display._getFileUrl(Display._theme.background_filename) + "')";
|
||||
break;
|
||||
case BackgroundType.Video:
|
||||
// never actually used since background type is overridden from video to transparent in window.py
|
||||
backgroundContent = Display._theme.background_border_color;
|
||||
backgroundHtml = "<video loop autoplay muted><source src='" + Display._theme.background_filename + "'></video>";
|
||||
backgroundHtml = "<video loop autoplay muted><source src='" + Display._getFileUrl(Display._theme.background_filename) + "'></video>";
|
||||
break;
|
||||
default:
|
||||
backgroundContent = "#000";
|
||||
|
@ -1393,7 +1393,16 @@ var Display = {
|
|||
value[1] = '/';
|
||||
value[2] = Object.keys(Display._slides).length;
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* Translates file:// protocol URLs to openlp-library://local-file/ scheme
|
||||
*/
|
||||
_getFileUrl: function(url) {
|
||||
if (url && (url.indexOf('file://') === 0)) {
|
||||
return url.replace('file://', 'openlp-library://local-file/');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
};
|
||||
new QWebChannel(qt.webChannelTransport, function (channel) {
|
||||
window.displayWatcher = channel.objects.displayWatcher;
|
||||
|
|
|
@ -23,10 +23,15 @@ Subclass of QWebEngineView. Adds some special eventhandling needed for screensho
|
|||
Heavily inspired by https://stackoverflow.com/questions/33467776/qt-qwebengine-render-after-scrolling/33576100#33576100
|
||||
"""
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets
|
||||
from PyQt5 import QtCore, QtWebEngineWidgets, QtWidgets, QtWebEngineCore
|
||||
from typing import Tuple
|
||||
|
||||
from openlp.core.common import Singleton
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.mime import get_mime_type
|
||||
from openlp.core.common.platform import is_win
|
||||
|
||||
|
||||
LOG_LEVELS = {
|
||||
|
@ -107,3 +112,180 @@ class WebEngineView(QtWebEngineWidgets.QWebEngineView):
|
|||
self._child = w
|
||||
w.installEventFilter(self)
|
||||
return super(WebEngineView, self).event(ev)
|
||||
|
||||
|
||||
class LocalSchemeHelper():
|
||||
def deny_access(self, request: QtWebEngineCore.QWebEngineUrlRequestJob, url: str, real_path: str):
|
||||
log.exception('{request} denied. Real Path: {path}'.format(request=url,
|
||||
path=real_path))
|
||||
request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.Error.RequestDenied)
|
||||
|
||||
def not_found(self, request: QtWebEngineCore.QWebEngineUrlRequestJob, url: str, real_path: str):
|
||||
log.exception('{request} not found. Real Path: {path}'.format(request=url,
|
||||
path=real_path))
|
||||
request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.Error.UrlNotFound)
|
||||
|
||||
def exception(self, request: QtWebEngineCore.QWebEngineUrlRequestJob, url: str, exception: Exception):
|
||||
log.exception('{request} failed: {error:d}; {message}'.format(request=url, error=str(type(exception)),
|
||||
message=str(exception)))
|
||||
request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.Error.RequestFailed)
|
||||
|
||||
def invalid(self, request: QtWebEngineCore.QWebEngineUrlRequestJob, url: str):
|
||||
log.exception('{request} is invalid.'.format(request=url))
|
||||
request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.Error.UrlInvalid)
|
||||
|
||||
def reply_file(self, request: QtWebEngineCore.QWebEngineUrlRequestJob, path: str) -> Tuple[QtCore.QBuffer, str]:
|
||||
raw_html = open(path, 'rb').read()
|
||||
buffer = QtCore.QBuffer(self)
|
||||
buffer.open(QtCore.QIODevice.OpenModeFlag.WriteOnly)
|
||||
buffer.write(raw_html)
|
||||
buffer.seek(0)
|
||||
buffer.close()
|
||||
mime_type = get_mime_type(path)
|
||||
request.reply(bytes(mime_type, 'utf-8'), buffer)
|
||||
|
||||
def request_path_to_file(self, base_path: str, url_path: str):
|
||||
if is_win() and not base_path:
|
||||
while len(url_path) > 0 and url_path[0] == '/':
|
||||
url_path = url_path[1:]
|
||||
return os.path.realpath(base_path + os.path.normpath(url_path))
|
||||
|
||||
|
||||
class OpenLPSchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler, LocalSchemeHelper):
|
||||
def __init__(self, root_paths, parent):
|
||||
super().__init__(parent)
|
||||
self.root_paths = {base_path: os.path.realpath(value) for base_path, value in root_paths.items()}
|
||||
|
||||
def requestStarted(self, request: QtWebEngineCore.QWebEngineUrlRequestJob) -> None:
|
||||
url = request.requestUrl()
|
||||
try:
|
||||
request_base_key = url.host()
|
||||
# Checking root to forbid relative path attacks
|
||||
base_real_path = self.root_paths[request_base_key]
|
||||
request_real_path = self.request_path_to_file(base_real_path, url.path())
|
||||
if not request_real_path.startswith(base_real_path):
|
||||
return self.deny_access(request, url, request_real_path)
|
||||
if not os.path.exists(request_real_path):
|
||||
return self.not_found(request, url, request_real_path)
|
||||
self.reply_file(request, request_real_path)
|
||||
return
|
||||
except Exception as exception:
|
||||
self.exception(request, url, exception)
|
||||
|
||||
|
||||
class OpenLPLibrarySchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler, LocalSchemeHelper):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
def requestStarted(self, request: QtWebEngineCore.QWebEngineUrlRequestJob) -> None:
|
||||
url = request.requestUrl()
|
||||
try:
|
||||
request_base_key = url.host()
|
||||
if request_base_key == 'local-file':
|
||||
# Checking root to forbid relative path attacks
|
||||
request_real_path = self.request_path_to_file('', url.path())
|
||||
if not os.path.exists(request_real_path):
|
||||
return self.not_found(request, url, request_real_path)
|
||||
return self.reply_file(request, request_real_path)
|
||||
return self.invalid(request, url)
|
||||
except Exception as exception:
|
||||
self.exception(request, url, exception)
|
||||
|
||||
|
||||
class WebViewCustomScheme(QtCore.QObject):
|
||||
"""
|
||||
Allows the registering of custom protocols inside WebView. Can be used mainly for
|
||||
circumventing default security measures placed on http(s):// or file:// protocols.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
scheme = QtWebEngineCore.QWebEngineUrlScheme(self.scheme_name)
|
||||
scheme.setSyntax(QtWebEngineCore.QWebEngineUrlScheme.Syntax.Host)
|
||||
self.set_scheme_flags(scheme)
|
||||
QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme)
|
||||
|
||||
def set_scheme_flags(self, scheme: QtWebEngineCore.QWebEngineUrlScheme):
|
||||
scheme.setFlags(QtWebEngineCore.QWebEngineUrlScheme.Flag.CorsEnabled
|
||||
| QtWebEngineCore.QWebEngineUrlScheme.Flag.ContentSecurityPolicyIgnored
|
||||
| QtWebEngineCore.QWebEngineUrlScheme.Flag.SecureScheme
|
||||
| QtWebEngineCore.QWebEngineUrlScheme.Flag.LocalScheme
|
||||
| QtWebEngineCore.QWebEngineUrlScheme.Flag.LocalAccessAllowed)
|
||||
|
||||
def create_scheme_handler(self):
|
||||
raise Exception('Needs to be implemented.')
|
||||
|
||||
def init_handler(self, profile=None):
|
||||
if profile is None:
|
||||
profile = QtWebEngineWidgets.QWebEngineProfile.defaultProfile()
|
||||
handler = profile.urlSchemeHandler(self.scheme_name)
|
||||
if handler is not None:
|
||||
profile.removeUrlSchemeHandler(handler)
|
||||
self.handler = self.create_scheme_handler()
|
||||
profile.installUrlSchemeHandler(self.scheme_name, self.handler)
|
||||
|
||||
|
||||
class OpenLPScheme(WebViewCustomScheme):
|
||||
scheme_name = b'openlp'
|
||||
|
||||
def __init__(self, root_paths, parent=None):
|
||||
super().__init__(parent)
|
||||
self.root_paths = root_paths
|
||||
|
||||
def create_scheme_handler(self):
|
||||
return OpenLPSchemeHandler(self.root_paths, self)
|
||||
|
||||
def set_root_path(self, base: str, path: str):
|
||||
if hasattr(self, 'handler'):
|
||||
self.handler.root_paths[base] = path
|
||||
self.root_paths[base] = path
|
||||
|
||||
|
||||
# Allows to circumvent default file-on-HTTP restrictions and/or provide remote/non-local file locations.
|
||||
class OpenLPLibraryScheme(WebViewCustomScheme):
|
||||
scheme_name = b'openlp-library'
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
def set_scheme_flags(self, scheme: QtWebEngineCore.QWebEngineUrlScheme):
|
||||
scheme.setFlags(QtWebEngineCore.QWebEngineUrlScheme.Flag.ContentSecurityPolicyIgnored)
|
||||
|
||||
def create_scheme_handler(self):
|
||||
return OpenLPLibrarySchemeHandler(self)
|
||||
|
||||
|
||||
def init_webview_custom_schemes():
|
||||
"""
|
||||
This inits the custom scheme protocols used in OpenLP WebEngines. It must happen before
|
||||
QApplication instantiation.
|
||||
"""
|
||||
openlp_root_paths = {
|
||||
"display": AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'display' / 'html'
|
||||
}
|
||||
# openlp:// protocol
|
||||
WebViewSchemes().register_scheme(OpenLPScheme(openlp_root_paths))
|
||||
# openlp-library:// protocol
|
||||
WebViewSchemes().register_scheme(OpenLPLibraryScheme())
|
||||
|
||||
|
||||
def set_webview_display_path(path):
|
||||
scheme = WebViewSchemes().get_scheme(OpenLPScheme.scheme_name)
|
||||
if (scheme):
|
||||
scheme.set_root_path('display', path)
|
||||
|
||||
|
||||
class WebViewSchemes(metaclass=Singleton):
|
||||
def __init__(self) -> None:
|
||||
self._registered_schemes = {}
|
||||
|
||||
def get_scheme(self, name) -> WebViewCustomScheme:
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode('utf-8')
|
||||
return self._registered_schemes[name] if name in self._registered_schemes else None
|
||||
|
||||
def register_scheme(self, scheme: WebViewCustomScheme):
|
||||
name = scheme.scheme_name
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode('utf-8')
|
||||
self._registered_schemes[name] = scheme
|
||||
|
|
|
@ -29,7 +29,6 @@ import re
|
|||
|
||||
from PyQt5 import QtCore, QtWebChannel, QtWidgets
|
||||
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.enum import ServiceItemType
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.mixins import LogMixin, RegistryProperties
|
||||
|
@ -146,8 +145,6 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
|
|||
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_X11NetWmWindowTypeDialog)
|
||||
if is_macosx():
|
||||
self.setAttribute(QtCore.Qt.WA_MacAlwaysShowToolWindow, True)
|
||||
# Need to import this inline to get around a QtWebEngine issue
|
||||
from openlp.core.display.webengine import WebEngineView
|
||||
self._is_initialised = False
|
||||
self._is_manual_close = False
|
||||
self._can_show_startup_screen = can_show_startup_screen
|
||||
|
@ -159,22 +156,22 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
|
|||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.webview = WebEngineView(self)
|
||||
self.webview = self.init_webengine()
|
||||
self.webview.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
self.webview.page().setBackgroundColor(QtCore.Qt.transparent)
|
||||
self.webview.display_clicked = self.disable_display
|
||||
self.layout.addWidget(self.webview)
|
||||
self.webview.loadFinished.connect(self.after_loaded)
|
||||
display_base_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'display' / 'html'
|
||||
self.display_path = display_base_path / 'display.html'
|
||||
self.checkerboard_path = display_base_path / 'checkerboard.png'
|
||||
self.openlp_splash_screen_path = display_base_path / 'openlp-splash-screen.png'
|
||||
self.display_path = 'openlp://display/display.html'
|
||||
self.checkerboard_path = 'openlp://display/checkerboard.png'
|
||||
self.openlp_splash_screen_path = 'openlp://display/openlp-splash-screen.png'
|
||||
self.channel = QtWebChannel.QWebChannel(self)
|
||||
self.display_watcher = DisplayWatcher(self)
|
||||
self.channel.registerObject('displayWatcher', self.display_watcher)
|
||||
self.webview.page().setWebChannel(self.channel)
|
||||
self.display_watcher.initialised.connect(self.on_initialised)
|
||||
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(self.display_path)))
|
||||
qUrl = QtCore.QUrl(self.display_path)
|
||||
self.set_url(qUrl)
|
||||
self.is_display = False
|
||||
self.scale = 1
|
||||
self.hide_mode = None
|
||||
|
@ -198,6 +195,16 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
|
|||
if not self._is_manual_close:
|
||||
event.ignore()
|
||||
|
||||
def init_webengine(self):
|
||||
# Need to import this inline to get around a QtWebEngine issue
|
||||
from openlp.core.display.webengine import WebEngineView, WebViewSchemes, OpenLPScheme, OpenLPLibraryScheme
|
||||
webview = WebEngineView(self)
|
||||
profile = webview.page().profile()
|
||||
WebViewSchemes().get_scheme(OpenLPScheme.scheme_name).init_handler(profile)
|
||||
WebViewSchemes().get_scheme(OpenLPLibraryScheme.scheme_name).init_handler(profile)
|
||||
|
||||
return webview
|
||||
|
||||
def _fix_font_name(self, font_name):
|
||||
"""
|
||||
Do some font machinations to see if we can fix the font name
|
||||
|
@ -260,9 +267,10 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
|
|||
bg_color = self.settings.value('core/logo background color')
|
||||
image = self.settings.value('core/logo file')
|
||||
if path_to_str(image).startswith(':'):
|
||||
image = self.openlp_splash_screen_path
|
||||
image_uri = self.openlp_splash_screen_path
|
||||
else:
|
||||
try:
|
||||
image_uri = image.as_uri()
|
||||
image_uri = image.as_uri().replace('file://', 'openlp-library://local-file/')
|
||||
except Exception:
|
||||
image_uri = ''
|
||||
# if set to hide logo on startup, do not send the logo
|
||||
|
|
|
@ -987,7 +987,7 @@ describe("Display.setImageSlides", function () {
|
|||
});
|
||||
|
||||
it("should add a list of images", function () {
|
||||
var slides = [{"path": "file:///openlp1.jpg"}, {"path": "file:///openlp2.jpg"}];
|
||||
var slides = [{"path": "openlp-library://local-file//openlp1.jpg"}, {"path": "openlp-library://local-file//openlp2.jpg"}];
|
||||
spyOn(Reveal, "sync");
|
||||
spyOn(Reveal, "slide");
|
||||
|
||||
|
@ -997,9 +997,9 @@ describe("Display.setImageSlides", function () {
|
|||
expect(Display._slides["1"]).toEqual(1);
|
||||
expect($(".slides > section > section").length).toEqual(2);
|
||||
expect($(".slides > section > section > img").length).toEqual(2);
|
||||
expect($(".slides > section > section > img")[0].getAttribute("src")).toEqual("file:///openlp1.jpg");
|
||||
expect($(".slides > section > section > img")[0].getAttribute("src")).toEqual("openlp-library://local-file//openlp1.jpg");
|
||||
expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;");
|
||||
expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg");
|
||||
expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("openlp-library://local-file//openlp2.jpg");
|
||||
expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("width: 100%; height: 100%; margin: 0; object-fit: contain;");
|
||||
expect(Reveal.sync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -1210,6 +1210,14 @@ describe("Display.toggleVideoMute", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("localFile", function() {
|
||||
it('should translate file:// protocol to openlp-library://local-file/ scheme', function() {
|
||||
const fileUrl = 'file:///home/path/to/image.png';
|
||||
const resultFile = Display._getFileUrl(fileUrl);
|
||||
expect(resultFile).toEqual('openlp-library://local-file//home/path/to/image.png');
|
||||
})
|
||||
})
|
||||
|
||||
describe("Reveal slidechanged event", function () {
|
||||
it("should swap footer content", function (done) {
|
||||
var slides = [
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2023 OpenLP Developers #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
Package to test the openlp.core.display.webengine package.
|
||||
"""
|
|
@ -226,7 +226,7 @@ def test_set_startup_screen(display_window_env, mock_settings):
|
|||
|
||||
# THEN: javascript should be run
|
||||
display_window.run_javascript.assert_called_once_with(
|
||||
'Display.setStartupSplashScreen("red", "file://{path}");'.format(path=expect_image_path))
|
||||
'Display.setStartupSplashScreen("red", "openlp-library://local-file/{path}");'.format(path=expect_image_path))
|
||||
|
||||
|
||||
def test_set_startup_screen_default_image(display_window_env, mock_settings):
|
||||
|
@ -237,13 +237,9 @@ def test_set_startup_screen_default_image(display_window_env, mock_settings):
|
|||
display_window = DisplayWindow()
|
||||
display_window._is_initialised = True
|
||||
display_window.run_javascript = MagicMock()
|
||||
if is_win():
|
||||
splash_screen_path = 'c:/default/splash_screen.png'
|
||||
expect_splash_screen_path = '/' + splash_screen_path
|
||||
else:
|
||||
splash_screen_path = '/default/splash_screen.png'
|
||||
splash_screen_path = 'openlp://display/openlp-splash-screen.png'
|
||||
expect_splash_screen_path = splash_screen_path
|
||||
display_window.openlp_splash_screen_path = Path(splash_screen_path)
|
||||
display_window.openlp_splash_screen_path = splash_screen_path
|
||||
settings = {
|
||||
'core/logo background color': 'blue',
|
||||
'core/logo file': Path(':/graphics/openlp-splash-screen.png'),
|
||||
|
@ -256,7 +252,7 @@ def test_set_startup_screen_default_image(display_window_env, mock_settings):
|
|||
|
||||
# THEN: javascript should be run
|
||||
display_window.run_javascript.assert_called_with(
|
||||
'Display.setStartupSplashScreen("blue", "file://{path}");'.format(path=expect_splash_screen_path))
|
||||
'Display.setStartupSplashScreen("blue", "{path}");'.format(path=expect_splash_screen_path))
|
||||
|
||||
|
||||
def test_set_startup_screen_missing(display_window_env, mock_settings):
|
||||
|
|
Loading…
Reference in New Issue