From 87bb0dcff799dc6927258875dc209f6a6bc8dee4 Mon Sep 17 00:00:00 2001 From: Corbin Wunderlich Date: Sun, 1 Dec 2024 20:48:01 -0500 Subject: [PATCH] Add pointer lock feature (game cursor) This makes first-person gaming over Xpra finally usable. --- html5/index.html | 269 +++++++++++++++++++++------------------------ html5/js/Client.js | 28 ++++- html5/js/Window.js | 4 +- 3 files changed, 153 insertions(+), 148 deletions(-) diff --git a/html5/index.html b/html5/index.html index 747c45b6..4a277819 100644 --- a/html5/index.html +++ b/html5/index.html @@ -108,149 +108,83 @@ -
-
- -
- -
- +
+ +
+
+ +
+ +
shadow pointer
@@ -412,7 +346,7 @@

Xpra Bug Report

return v; }; - const float_menu_item_count = 6; + const float_menu_item_count = 7; const float_menu_item_size = 30; const float_menu_padding = 20; let float_menu_width = float_menu_item_size * float_menu_item_count + float_menu_padding; @@ -1457,7 +1391,9 @@

Xpra Bug Report

}); } else { $("#float_menu_button").hide(); - float_menu_element.css({ "max-width": "202px" }); + float_menu_element.css({ + "max-width": "232px" + }); } if (!floating_menu) { //nothing to do, it starts hidden @@ -1493,12 +1429,15 @@

Xpra Bug Report

} let client; + function init_page() { clog("initializing page"); const touchaction = getparam("touchaction") || "scroll"; touchaction_scroll = touchaction == "scroll"; set_touchaction(); + window.cursor_lock = false; + init_auth_autosubmit(); client = init_client(); @@ -1587,6 +1526,48 @@

Xpra Bug Report

client.read_clipboard(e); }); + $(".windowinfocus").children("canvas").on("click", function(e) { + const isPointerLocked = Boolean(document.pointerLockElement); + + if (isPointerLocked) { + return; + } + + if (window.cursor_lock && !isPointerLocked) { + const focusedWindow = document.getElementById(client.topwindow.toString()); + focusedWindow.querySelector("canvas").requestPointerLock(); + + $("#cursor_lock_button").removeClass("icon-paused"); + } + }); + + $("#cursor_lock_button").on("click", function(e) { + if (!window.cursor_lock) { + window.cursor_lock = true; + + window.alert("You are now in cursor lock mode. Press ESC to temporarily exit, and click on the screen to enter it again."); + + const focusedWindow = client.id_to_window[client.topwindow]; + focusedWindow.canvas.requestPointerLock(); + + $("#cursor_lock_button").addClass("icon-toggled"); + + return; + } + + window.cursor_lock = false; + + document.exitPointerLock(); + + $("#cursor_lock_button").removeClass("icon-toggled icon-paused"); + }); + + document.addEventListener("pointerlockchange", function(e) { + if (!(Boolean(document.pointerLockElement)) && window.cursor_lock) { + $("#cursor_lock_button").addClass("icon-paused"); + } + }); + // Configure Xpra tray window list right click behavior. $("#open_windows_list") .siblings("a") diff --git a/html5/js/Client.js b/html5/js/Client.js index c5544c05..c68c26da 100644 --- a/html5/js/Client.js +++ b/html5/js/Client.js @@ -326,7 +326,7 @@ class XpraClient { const screen_element = jQuery("#screen"); screen_element.mousedown((e) => this.on_mousedown(e)); screen_element.mouseup((e) => this.on_mouseup(e)); - screen_element.mousemove((e) => this.on_mousemove(e)); + document.getElementById("screen").addEventListener("mousemove", (e) => this.on_mousemove(e)); const div = document.querySelector("#screen"); function on_mousescroll(e) { @@ -1590,10 +1590,17 @@ class XpraClient { * Mouse handlers */ getMouse(e) { + const windowIsLocked = Boolean(document.pointerLockElement); + // get mouse position take into account scroll let mx = e.clientX + jQuery(document).scrollLeft(); let my = e.clientY + jQuery(document).scrollTop(); + if (windowIsLocked) { + mx = e.movementX; + my = e.movementY; + } + if (this.scale !== 1) { mx = Math.round(mx * this.scale); my = Math.round(my * this.scale); @@ -1611,6 +1618,10 @@ class XpraClient { my = 0; } } else { + if (windowIsLocked) { + this.last_mouse_x += mx; + this.last_mouse_y += my; + } this.last_mouse_x = mx; this.last_mouse_y = my; } @@ -1623,8 +1634,15 @@ class XpraClient { // IE, Opera (zero based) mbutton = Math.max(0, e.button) + 1; + mx = this.last_mouse_x; + my = this.last_mouse_y; + // We return a simple javascript object (a hash) with x and y defined - return { x: mx, y: my, button: mbutton }; + return { + x: mx, + y: my, + button: mbutton + }; } on_mousedown(e, win) { @@ -1719,6 +1737,12 @@ class XpraClient { if (wid > 0 && this.focus != wid) { this.set_focus(win); } + + if (window.cursor_lock) { + $("#cursor-lock-button").removeClass("icon-paused"); + win.canvas.requestPointerLock(); + } + let button = mouse.button; const lbe = this.last_button_event; if (lbe[0] == button && lbe[1] == pressed && lbe[2] == x && lbe[3] == y) { diff --git a/html5/js/Window.js b/html5/js/Window.js index d653b8dc..9453041f 100644 --- a/html5/js/Window.js +++ b/html5/js/Window.js @@ -351,13 +351,13 @@ class XpraWindow { this.pointer_last_y = event_.offsetY; } }); - canvas.addEventListener("pointermove", (event_) => { + canvas.addEventListener("mousemove", (event_) => { this.debug("mouse", "pointermove:", event_); if (this.pointer_down == event_.pointerId) { const dx = event_.offsetX - this.pointer_last_x; const dy = event_.offsetY - this.pointer_last_y; this.pointer_last_x = event_.offsetX; - this.pointer_last_y = event_.offsetY; + this.pointer_last_y = event_.offsetX; const mult = 20 * (window.devicePixelRatio || 1); event_.wheelDeltaX = Math.round(dx * mult); event_.wheelDeltaY = Math.round(dy * mult);