import QtQuick import Konvergo 1.0 import QtWebEngine import QtWebChannel import QtQuick.Window import QtQuick.Controls import Qt.labs.platform as Labs Window { id: mainWindow title: "Jellyfin Desktop" objectName: "mainWindow" width: 1280 height: 720 minimumWidth: 213 minimumHeight: 120 visible: true color: "#000000" // Properties previously from KonvergoWindow property bool webDesktopMode: true property bool showDebugLayer: false property string debugInfo: "" property string videoInfo: "" property string webUrl: "" property bool showSystemTrayIcon: webDesktopMode && components.system.isWindows && components.settings.windowsTrayIcon signal reloadWebClient() Component.onCompleted: { if (components && components.settings) { webUrl = components.settings.getWebClientUrl(webDesktopMode) } } onClosing: function(close) { if (showSystemTrayIcon) { // Minimize to tray on close. close.accepted = false mainWindow.hide() } } function toggleFullscreen() { visibility = (visibility === Window.FullScreen) ? Window.Windowed : Window.FullScreen } function toggleDebug() { showDebugLayer = !showDebugLayer } function setFullScreen(enable) { visibility = enable ? Window.FullScreen : Window.Windowed } function minimizeWindow() { if (visibility !== Window.FullScreen) visibility = Window.Minimized } function restoreWindow() { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } function runWebAction(action) { if (mainWindow.webDesktopMode) web.triggerWebAction(action) } Action { enabled: mainWindow.webDesktopMode shortcut: { if (components.system.isMacos) return "Ctrl+Meta+F" return "F11" } onTriggered: mainWindow.toggleFullscreen() } Action { shortcut: "Alt+Return" enabled: { if (mainWindow.webDesktopMode && components.system.isWindows) return true; return false; } onTriggered: mainWindow.toggleFullscreen() } Action { enabled: mainWindow.webDesktopMode shortcut: StandardKey.Close onTriggered: mainWindow.close() } Action { enabled: mainWindow.webDesktopMode shortcut: { if (components.system.isMacos) return "Ctrl+M"; return "Meta+Down"; } onTriggered: mainWindow.minimizeWindow() } Action { enabled: mainWindow.webDesktopMode shortcut: components.system.isWindows ? "Ctrl+Q" : StandardKey.Quit onTriggered: Qt.quit() } Action { shortcut: "Ctrl+Shift+D" enabled: mainWindow.webDesktopMode onTriggered: mainWindow.toggleDebug() } Action { shortcut: StandardKey.Copy onTriggered: runWebAction(WebEngineView.Copy) id: action_copy } Action { shortcut: StandardKey.Cut onTriggered: runWebAction(WebEngineView.Cut) id: action_cut } Action { shortcut: StandardKey.Paste onTriggered: runWebAction(WebEngineView.Paste) id: action_paste } Action { shortcut: StandardKey.SelectAll onTriggered: runWebAction(WebEngineView.SelectAll) id: action_selectall } Action { shortcut: StandardKey.Undo onTriggered: runWebAction(WebEngineView.Undo) id: action_undo } Action { shortcut: StandardKey.Redo onTriggered: runWebAction(WebEngineView.Redo) id: action_redo } Action { shortcut: StandardKey.Back onTriggered: runWebAction(WebEngineView.Back) id: action_back } Action { shortcut: StandardKey.Forward onTriggered: runWebAction(WebEngineView.Forward) id: action_forward } Action { enabled: mainWindow.webDesktopMode shortcut: "Ctrl+0" onTriggered: web.zoomFactor = 1.0 } WebChannel { id: webChannelObject } Binding { target: web property: "zoomFactor" value: 1.0 when: !components.settings.allowBrowserZoom() } MpvVideoItem { id: video objectName: "video" enabled: true width: mainWindow.contentItem.width height: mainWindow.contentItem.height anchors.left: mainWindow.contentItem.left anchors.right: mainWindow.contentItem.right anchors.top: mainWindow.contentItem.top Component.onCompleted: { console.log("MpvVideoItem size:", width, "x", height, "visible:", visible) } onWidthChanged: console.log("MpvVideoItem width changed:", width) onHeightChanged: console.log("MpvVideoItem height changed:", height) } WebEngineView { id: web objectName: "web" width: mainWindow.width height: mainWindow.height z: 100 backgroundColor: "transparent" // this is needed to prevent intermittent(?) black screens when unminizing // or resumsing from suspend (linux/{x11/wayland}, possibly others). layer.enabled: true webChannel: webChannelObject settings.errorPageEnabled: false settings.localContentCanAccessRemoteUrls: true settings.localContentCanAccessFileUrls: true settings.allowRunningInsecureContent: true settings.playbackRequiresUserGesture: false profile.httpUserAgent: components.system.getUserAgent() profile.httpCacheType: WebEngineProfile.DiskHttpCache url: mainWindow.webUrl focus: true property string currentHoveredUrl: "" onLinkHovered: function(hoveredUrl) { web.currentHoveredUrl = hoveredUrl; } profile.persistentCookiesPolicy: WebEngineProfile.AllowPersistentCookies profile.offTheRecord: false profile.storageName: "JellyfinDesktopStorage" Component.onCompleted: { console.log("WebEngineView size:", width, "x", height, "backgroundColor:", backgroundColor) forceActiveFocus() mainWindow.reloadWebClient.connect(reload) // Handle CSP workaround from C++ components.system.pageContentReady.connect(function(html, finalUrl, hadCSP) { if (hadCSP) { console.log("CSP workaround: navigating to", finalUrl); web.url = finalUrl; } }) var nativeshell = { sourceCode: components.system.getNativeShellScript(), injectionPoint: WebEngineScript.DocumentCreation, worldId: WebEngineScript.MainWorld } web.profile.userScripts.collection = [ nativeshell ]; } onLoadingChanged: function(loadingInfo) { // we use a timer here to switch to the webview since // it take a few moments for the webview to render // after it has loaded. // if (loadingInfo.status == WebEngineView.LoadStartedStatus) { console.log("WebEngineLoadRequest starting: " + loadingInfo.url); } else if (loadingInfo.status == WebEngineView.LoadSucceededStatus) { console.log("WebEngineLoadRequest success: " + loadingInfo.url); } else if (loadingInfo.status == WebEngineView.LoadFailedStatus) { console.log("WebEngineLoadRequest failure: " + loadingInfo.url + " error code: " + loadingInfo.errorCode); errorLabel.visible = true errorLabel.text = "Error loading client, this is bad and should not happen
" + "You can try to reload or head to our support page

Actual Error:
" +
                          loadingInfo.errorString + " [" + loadingInfo.errorCode + "]


" + "Provide the logfile as well." } } onNewWindowRequested: { if (request.userInitiated) { console.log("Opening external URL: " + web.currentHoveredUrl) components.system.openExternalUrl(web.currentHoveredUrl) } } onFullScreenRequested: { console.log("Request fullscreen: " + request.toggleOn) mainWindow.setFullScreen(request.toggleOn) request.accept() } onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) { components.system.jsLog(level, sourceID + ":" + lineNumber + " " + message); } onCertificateError: function(error) { console.log(error.url + " :" + error.description + error.error) if (components.settings.ignoreSSLErrors()) { error.acceptCertificate() } } } Text { id: errorLabel z: 5 anchors.centerIn: parent color: "#999999" linkColor: "#a85dc3" text: "Generic error" font.pixelSize: 32 font.bold: true visible: false verticalAlignment: Text.AlignVCenter textFormat: Text.StyledText onLinkActivated: { if (url == "reload") { errorLabel.visible = false web.reload() } else { Qt.openUrlExternally(url) } } } Rectangle { id: debug color: "black" z: 10 anchors.centerIn: parent width: parent.width height: parent.height opacity: 0.7 visible: mainWindow.showDebugLayer Text { id: debugLabel width: (parent.width - 50) / 2 height: parent.height - 25 anchors.left: parent.left anchors.leftMargin: 64 anchors.top: parent.top anchors.topMargin: 54 anchors.bottomMargin: 54 color: "white" font.pixelSize: Math.round(height / 65) wrapMode: Text.WrapAnywhere function windowDebug() { var dbg = mainWindow.debugInfo + "Window and web\n"; dbg += " Window size: " + parent.width + "x" + parent.height + " - " + web.width + "x" + web.height + "\n"; dbg += " DevicePixel ratio: " + Screen.devicePixelRatio + "\n"; return dbg; } text: windowDebug() } Text { id: videoLabel width: (parent.width - 50) / 2 height: parent.height - 25 anchors.right: parent.right anchors.left: debugLabel.right anchors.rightMargin: 64 anchors.top: parent.top anchors.topMargin: 54 anchors.bottomMargin: 54 color: "white" font.pixelSize: Math.round(height / 65) wrapMode: Text.WrapAnywhere text: mainWindow.videoInfo } } property QtObject webChannel: web.webChannel Labs.SystemTrayIcon { visible: showSystemTrayIcon icon.source: "qrc:/images/icon.png" tooltip: "Jellyfin Desktop" onActivated: function(reason) { if (reason === Labs.SystemTrayIcon.Context) { // Right click: open context menu contextMenu.open() components.window.setCursorVisibility(true) } else { // All other clicks: restore window restoreWindow() } } menu: Labs.Menu { id: contextMenu Labs.MenuItem { text: qsTr("Restore") onTriggered: restoreWindow() } Labs.MenuSeparator {} Labs.MenuItem { text: qsTr("Quit") onTriggered: Qt.quit() } } } }