From a7d443ab231a1666c2678ee2f75c802ab5c36f52 Mon Sep 17 00:00:00 2001 From: Bradley Eaton Date: Tue, 24 Jun 2025 13:27:19 +0100 Subject: [PATCH 1/9] Use keycodes for UWP xbox instead of gamepadtokey. --- src/controllers/playback/video/index.js | 6 +++ src/elements/emby-select/emby-select.js | 3 +- src/elements/emby-slider/emby-slider.js | 6 +++ src/plugins/bookPlayer/plugin.js | 6 +++ src/plugins/pdfPlayer/plugin.js | 6 +++ src/scripts/keyboardNavigation.js | 49 ++++++++++++++++++++++++- 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index c0be4c579..37e9b27a7 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1243,6 +1243,12 @@ export default function (view) { if (layoutManager.tv && !currentVisibleMenu) { // Change the behavior of some keys when the OSD is hidden switch (key) { + case 'NavigationLeft': + case 'NavigationRight': + case 'GamepadLeftThumbLeft': + case 'GamepadLeftThumbRight': + case 'GamepadDPadLeft': + case 'GamepadDPadRight': case 'ArrowLeft': case 'ArrowRight': if (!e.shiftKey) { diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index b845c68c1..45010ae14 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -79,7 +79,8 @@ function onMouseDown(e) { } function onKeyDown(e) { - if (e.keyCode === 13 && !enableNativeMenu()) { + // Xbox controller for UWP WebView2 uses keycode 195 to select. + if (e.keyCode === 13 && !enableNativeMenu() || (e.keyCode === 195 && browser.edgeChromium)) { e.preventDefault(); showActionSheet(this); } diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index e4575ecaf..5ae0cc12e 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -527,12 +527,18 @@ function stepKeyboard(elem, delta) { */ function onKeyDown(e) { switch (keyboardnavigation.getKeyName(e)) { + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbLeft': case 'ArrowLeft': case 'Left': stepKeyboard(this, -normalizeSliderStep(this, this.keyboardStepDown)); e.preventDefault(); e.stopPropagation(); break; + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbRight': case 'ArrowRight': case 'Right': stepKeyboard(this, normalizeSliderStep(this, this.keyboardStepUp)); diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index 1e0ad2a00..a0f7b4959 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -138,12 +138,18 @@ export class BookPlayer { if (!this.loaded) return; switch (key) { + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbRight': case 'l': case 'ArrowRight': case 'Right': e.preventDefault(); this.next(); break; + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbLeft': case 'j': case 'ArrowLeft': case 'Left': diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index f77c01364..17558ec97 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -97,12 +97,18 @@ export class PdfPlayer { const key = keyboardnavigation.getKeyName(e); switch (key) { + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbRight': case 'l': case 'ArrowRight': case 'Right': e.preventDefault(); this.next(); break; + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbLeft': case 'j': case 'ArrowLeft': case 'Left': diff --git a/src/scripts/keyboardNavigation.js b/src/scripts/keyboardNavigation.js index a07a8a270..d2f9882ea 100644 --- a/src/scripts/keyboardNavigation.js +++ b/src/scripts/keyboardNavigation.js @@ -20,6 +20,31 @@ const KeyNames = { 38: 'ArrowUp', 39: 'ArrowRight', 40: 'ArrowDown', + + // UWP WebView section start -- + // Navigation Up/Down/Left/Right is part of TVJS directionalnavigation-1.0.0.0.js + // Unsure what this is used for. Media remote? + 138: 'NavigationUp', + 139: 'NavigationDown', + 140: 'NavigationLeft', + 141: 'NavigationRight', + 195: 'GamepadA', + // Currently Xbox UWP WebView 2 sends code 27 (Escape instead) despite being undocumented + // Desktop UWP unchanged + 196: 'GamepadB', + 203: 'GamepadDPadUp', + 204: 'GamepadDPadDown', + 205: 'GamepadDPadLeft', + 206: 'GamepadDPadRight', + // Currently Xbox UWP WebView 2 sends Arrow keycodes despite being undocumented + // Desktop UWP unchanged + // Left Thumbstick + 211: 'GamepadLeftThumbUp', + 212: 'GamepadLeftThumbDown', + 214: 'GamepadLeftThumbLeft', + 213: 'GamepadLeftThumbRight', + // End of UWP WebView Section + // MediaRewind (Tizen/WebOS) 412: 'MediaRewind', // MediaStop (Tizen/WebOS) @@ -43,7 +68,10 @@ const KeyNames = { /** * Keys used for keyboard navigation. */ -const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BrowserHome', 'Find']; +const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BrowserHome', 'Find', + 'NavigationUp', 'NavigationDown', 'NavigationLeft', 'NavigationRight', + 'GamepadDPadUp', 'GamepadDPadDown', 'GamepadDPadLeft', 'GamepadDPadRight', + 'GamepadLeftThumbUp', 'GamepadLeftThumbDown', 'GamepadLeftThumbLeft', 'GamepadLeftThumbRight']; /** * Keys used for media playback control. @@ -145,6 +173,9 @@ export function enable() { let capture = true; switch (key) { + case 'NavigationLeft': + case 'GamepadDPadLeft': + case 'GamepadLeftThumbLeft': case 'ArrowLeft': if (!isInteractiveElement(document.activeElement)) { inputManager.handleCommand('left'); @@ -152,9 +183,15 @@ export function enable() { capture = false; } break; + case 'NavigationUp': + case 'GamepadDPadUp': + case 'GamepadLeftThumbUp': case 'ArrowUp': inputManager.handleCommand('up'); break; + case 'NavigationRight': + case 'GamepadDPadRight': + case 'GamepadLeftThumbRight': case 'ArrowRight': if (!isInteractiveElement(document.activeElement)) { inputManager.handleCommand('right'); @@ -162,10 +199,17 @@ export function enable() { capture = false; } break; + case 'NavigationDown': + case 'GamepadDPadDown': + case 'GamepadLeftThumbDown': case 'ArrowDown': inputManager.handleCommand('down'); break; + case 'GamepadA': + inputManager.handleCommand('select'); + break; + case 'GamepadB': case 'Back': inputManager.handleCommand('back'); break; @@ -239,7 +283,8 @@ function attachGamepadScript() { } // No need to check for gamepads manually at load time, the eventhandler will be fired for that -if (navigator.getGamepads && appSettings.enableGamepad()) { +// Not needed for UWP +if (navigator.getGamepads && appSettings.enableGamepad() && !browser.xboxOne) { window.addEventListener('gamepadconnected', attachGamepadScript); } From eff4ca2f1971c47047b048fd77124a48bd8f5fb2 Mon Sep 17 00:00:00 2001 From: Bradley Eaton Date: Mon, 30 Jun 2025 16:07:37 +0100 Subject: [PATCH 2/9] Use keyboard key names for navigation on controller. --- src/controllers/playback/video/index.js | 6 --- src/elements/emby-slider/emby-slider.js | 6 --- src/plugins/bookPlayer/plugin.js | 6 --- src/plugins/pdfPlayer/plugin.js | 6 --- src/scripts/keyboardNavigation.js | 52 ++++++++++--------------- 5 files changed, 20 insertions(+), 56 deletions(-) diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 37e9b27a7..c0be4c579 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1243,12 +1243,6 @@ export default function (view) { if (layoutManager.tv && !currentVisibleMenu) { // Change the behavior of some keys when the OSD is hidden switch (key) { - case 'NavigationLeft': - case 'NavigationRight': - case 'GamepadLeftThumbLeft': - case 'GamepadLeftThumbRight': - case 'GamepadDPadLeft': - case 'GamepadDPadRight': case 'ArrowLeft': case 'ArrowRight': if (!e.shiftKey) { diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 5ae0cc12e..e4575ecaf 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -527,18 +527,12 @@ function stepKeyboard(elem, delta) { */ function onKeyDown(e) { switch (keyboardnavigation.getKeyName(e)) { - case 'NavigationLeft': - case 'GamepadDPadLeft': - case 'GamepadLeftThumbLeft': case 'ArrowLeft': case 'Left': stepKeyboard(this, -normalizeSliderStep(this, this.keyboardStepDown)); e.preventDefault(); e.stopPropagation(); break; - case 'NavigationRight': - case 'GamepadDPadRight': - case 'GamepadLeftThumbRight': case 'ArrowRight': case 'Right': stepKeyboard(this, normalizeSliderStep(this, this.keyboardStepUp)); diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index a0f7b4959..1e0ad2a00 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -138,18 +138,12 @@ export class BookPlayer { if (!this.loaded) return; switch (key) { - case 'NavigationRight': - case 'GamepadDPadRight': - case 'GamepadLeftThumbRight': case 'l': case 'ArrowRight': case 'Right': e.preventDefault(); this.next(); break; - case 'NavigationLeft': - case 'GamepadDPadLeft': - case 'GamepadLeftThumbLeft': case 'j': case 'ArrowLeft': case 'Left': diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index 17558ec97..f77c01364 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -97,18 +97,12 @@ export class PdfPlayer { const key = keyboardnavigation.getKeyName(e); switch (key) { - case 'NavigationRight': - case 'GamepadDPadRight': - case 'GamepadLeftThumbRight': case 'l': case 'ArrowRight': case 'Right': e.preventDefault(); this.next(); break; - case 'NavigationLeft': - case 'GamepadDPadLeft': - case 'GamepadLeftThumbLeft': case 'j': case 'ArrowLeft': case 'Left': diff --git a/src/scripts/keyboardNavigation.js b/src/scripts/keyboardNavigation.js index d2f9882ea..ac21b112e 100644 --- a/src/scripts/keyboardNavigation.js +++ b/src/scripts/keyboardNavigation.js @@ -24,25 +24,30 @@ const KeyNames = { // UWP WebView section start -- // Navigation Up/Down/Left/Right is part of TVJS directionalnavigation-1.0.0.0.js // Unsure what this is used for. Media remote? - 138: 'NavigationUp', - 139: 'NavigationDown', - 140: 'NavigationLeft', - 141: 'NavigationRight', + 138: 'ArrowUp', + 139: 'ArrowDown', + 140: 'ArrowLeft', + 141: 'ArrowRight', + 195: 'GamepadA', // Currently Xbox UWP WebView 2 sends code 27 (Escape instead) despite being undocumented // Desktop UWP unchanged - 196: 'GamepadB', - 203: 'GamepadDPadUp', - 204: 'GamepadDPadDown', - 205: 'GamepadDPadLeft', - 206: 'GamepadDPadRight', + // GamepadB + 196: 'Escape', + + // DPad Up/Down/Left/Right + 203: 'ArrowUp', + 204: 'ArrowDown', + 205: 'ArrowLeft', + 206: 'ArrowRight', + // Currently Xbox UWP WebView 2 sends Arrow keycodes despite being undocumented // Desktop UWP unchanged - // Left Thumbstick - 211: 'GamepadLeftThumbUp', - 212: 'GamepadLeftThumbDown', - 214: 'GamepadLeftThumbLeft', - 213: 'GamepadLeftThumbRight', + // Left Thumbstick Up/Down/Left/Right + 211: 'ArrowUp', + 212: 'ArrowDown', + 214: 'ArrowLeft', + 213: 'ArrowRight', // End of UWP WebView Section // MediaRewind (Tizen/WebOS) @@ -68,11 +73,7 @@ const KeyNames = { /** * Keys used for keyboard navigation. */ -const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BrowserHome', 'Find', - 'NavigationUp', 'NavigationDown', 'NavigationLeft', 'NavigationRight', - 'GamepadDPadUp', 'GamepadDPadDown', 'GamepadDPadLeft', 'GamepadDPadRight', - 'GamepadLeftThumbUp', 'GamepadLeftThumbDown', 'GamepadLeftThumbLeft', 'GamepadLeftThumbRight']; - +const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BrowserHome', 'Find']; /** * Keys used for media playback control. */ @@ -173,9 +174,6 @@ export function enable() { let capture = true; switch (key) { - case 'NavigationLeft': - case 'GamepadDPadLeft': - case 'GamepadLeftThumbLeft': case 'ArrowLeft': if (!isInteractiveElement(document.activeElement)) { inputManager.handleCommand('left'); @@ -183,15 +181,9 @@ export function enable() { capture = false; } break; - case 'NavigationUp': - case 'GamepadDPadUp': - case 'GamepadLeftThumbUp': case 'ArrowUp': inputManager.handleCommand('up'); break; - case 'NavigationRight': - case 'GamepadDPadRight': - case 'GamepadLeftThumbRight': case 'ArrowRight': if (!isInteractiveElement(document.activeElement)) { inputManager.handleCommand('right'); @@ -199,9 +191,6 @@ export function enable() { capture = false; } break; - case 'NavigationDown': - case 'GamepadDPadDown': - case 'GamepadLeftThumbDown': case 'ArrowDown': inputManager.handleCommand('down'); break; @@ -209,7 +198,6 @@ export function enable() { case 'GamepadA': inputManager.handleCommand('select'); break; - case 'GamepadB': case 'Back': inputManager.handleCommand('back'); break; From 54b6d4015cd1954250ec49b37893b8027761dbfd Mon Sep 17 00:00:00 2001 From: Bradley Eaton Date: Mon, 30 Jun 2025 16:37:47 +0100 Subject: [PATCH 3/9] Enable browser detection on UWP WebView 2. --- src/scripts/browser.js | 2 +- src/scripts/keyboardNavigation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 5525e3ec0..75f7a1b27 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -290,7 +290,7 @@ browser.hisense = userAgent.toLowerCase().includes('hisense'); browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || window.tizen != null; browser.vidaa = userAgent.toLowerCase().includes('vidaa'); browser.web0s = isWeb0s(); -browser.edgeUwp = browser.edge && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1); +browser.edgeUwp = (browser.edge || browser.edgeChromium) && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1); if (browser.web0s) { browser.web0sVersion = web0sVersion(browser); diff --git a/src/scripts/keyboardNavigation.js b/src/scripts/keyboardNavigation.js index ac21b112e..593978400 100644 --- a/src/scripts/keyboardNavigation.js +++ b/src/scripts/keyboardNavigation.js @@ -272,7 +272,7 @@ function attachGamepadScript() { // No need to check for gamepads manually at load time, the eventhandler will be fired for that // Not needed for UWP -if (navigator.getGamepads && appSettings.enableGamepad() && !browser.xboxOne) { +if (navigator.getGamepads && appSettings.enableGamepad() && !browser.edgeUwp) { window.addEventListener('gamepadconnected', attachGamepadScript); } From 12bbcb0048b396fa113ee363fb5f9ed99d5ea361 Mon Sep 17 00:00:00 2001 From: Bradley Eaton Date: Mon, 30 Jun 2025 16:44:54 +0100 Subject: [PATCH 4/9] Explicitly disable native drop down menu on UWP. --- src/elements/emby-select/emby-select.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index 45010ae14..e574a4b87 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -7,8 +7,9 @@ import 'webcomponents.js/webcomponents-lite'; const EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); function enableNativeMenu() { + // WebView 2 creates dropdown that doesn't work with controller. if (browser.edgeUwp || browser.xboxOne) { - return true; + return false; } // Doesn't seem to work at all @@ -80,7 +81,7 @@ function onMouseDown(e) { function onKeyDown(e) { // Xbox controller for UWP WebView2 uses keycode 195 to select. - if (e.keyCode === 13 && !enableNativeMenu() || (e.keyCode === 195 && browser.edgeChromium)) { + if ((e.keyCode === 13 || e.keyCode === 195) && !enableNativeMenu()) { e.preventDefault(); showActionSheet(this); } From fe3adb2ba67e31a9de39dc10599cdc2fee33eb52 Mon Sep 17 00:00:00 2001 From: Bradley Eaton Date: Mon, 30 Jun 2025 17:04:15 +0100 Subject: [PATCH 5/9] Hide enableGamepad on UWP and DisplayMode on Xbox. --- src/components/apphost.js | 6 +++++- src/controllers/user/controls/index.html | 2 +- src/controllers/user/controls/index.js | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/apphost.js b/src/components/apphost.js index addc352c1..5b59b4fec 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -265,7 +265,11 @@ const supportedFeatures = function () { } features.push(AppFeature.DisplayLanguage); - features.push(AppFeature.DisplayMode); + + if (!browser.xboxOne) { + features.push(AppFeature.DisplayMode); + } + features.push(AppFeature.TargetBlank); features.push(AppFeature.Screensaver); diff --git a/src/controllers/user/controls/index.html b/src/controllers/user/controls/index.html index 512f3df05..5ebaad84e 100644 --- a/src/controllers/user/controls/index.html +++ b/src/controllers/user/controls/index.html @@ -6,7 +6,7 @@ ${Controls} -
+