diff --git a/app/ui.js b/app/ui.js index b69997c0..d8b77eee 100644 --- a/app/ui.js +++ b/app/ui.js @@ -41,6 +41,7 @@ import KeyTable from "../core/input/keysym.js"; import keysyms from "../core/input/keysymdef.js"; import Keyboard from "../core/input/keyboard.js"; import RFB from "../core/rfb.js"; +import MouseButtonMapper from "../core/mousebuttonmapper.js"; import * as WebUtil from "./webutil.js"; const PAGE_TITLE = "KasmVNC"; @@ -264,6 +265,21 @@ const UI = { UI.setupSettingLabels(); UI.updateQuality(); }, + initMouseButtonMapper() { + const mouseButtonMapper = new MouseButtonMapper(); + + const settings = WebUtil.readSetting("mouseButtonMapper"); + if (settings) { + mouseButtonMapper.load(settings); + return mouseButtonMapper; + } + + mouseButtonMapper.set(3, 7); + mouseButtonMapper.set(4, 8); + WebUtil.writeSetting("mouseButtonMapper", mouseButtonMapper.dump()); + + return mouseButtonMapper; + }, // Adds a link to the label elements on the corresponding input elements setupSettingLabels() { const labels = document.getElementsByTagName('LABEL'); @@ -1381,6 +1397,7 @@ const UI = { UI.rfb.keyboard.enableIME = UI.getSetting('enable_ime'); UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless; UI.rfb.enableWebRTC = UI.getSetting('enable_webrtc'); + UI.rfb.mouseButtonMapper = UI.initMouseButtonMapper(); //Only explicitly request permission to clipboard on browsers that support binary clipboard access if (supportsBinaryClipboard()) { diff --git a/core/mousebuttonmapper.js b/core/mousebuttonmapper.js new file mode 100644 index 00000000..6b7fe1ee --- /dev/null +++ b/core/mousebuttonmapper.js @@ -0,0 +1,49 @@ +export default class MouseButtonMapper { + constructor() { + this.map = new Map(); + } + + get(mouseButton) { + if (!this.map.has(mouseButton)) { + return mouseButton; + } + + return this.map.get(mouseButton); + } + + set(mouseButton, xorgMouseButton) { + return this.map.set(mouseButton, xorgMouseButton); + } + + delete(mouseButton) { + return this.map.delete(mouseButton); + } + + dump() { + return JSON.stringify(this.map, this._replacer); + } + + load(json) { + this.map = JSON.parse(json, this._reviver); + } + + _replacer(key, value) { + if (!(value instanceof Map)) { + return value; + } + + return { + dataType: 'Map', + value: Array.from(value.entries()) + }; + } + + _reviver(key, value) { + if (typeof value === 'object' && value !== null) { + if (value.dataType === 'Map') { + return new Map(value.value); + } + } + return value; + } +} diff --git a/core/rfb.js b/core/rfb.js index b72820eb..252beb04 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -26,6 +26,7 @@ import DES from "./des.js"; import KeyTable from "./input/keysym.js"; import XtScancode from "./input/xtscancodes.js"; import { encodings } from "./encodings.js"; +import MouseButtonMapper from "./mousebuttonmapper.js"; import RawDecoder from "./decoders/raw.js"; import CopyRectDecoder from "./decoders/copyrect.js"; @@ -192,6 +193,7 @@ export default class RFB extends EventTargetMixin { this._viewportHasMoved = false; this._accumulatedWheelDeltaX = 0; this._accumulatedWheelDeltaY = 0; + this.mouseButtonMapper = new MouseButtonMapper(); // Gesture state this._gestureLastTapTime = null; @@ -1558,6 +1560,7 @@ export default class RFB extends EventTargetMixin { this._canvas); } + const mappedButton = this.mouseButtonMapper.get(ev.button); switch (ev.type) { case 'mousedown': setCapture(this._canvas); @@ -1575,11 +1578,11 @@ export default class RFB extends EventTargetMixin { this.checkLocalClipboard(); this._handleMouseButton(pos.x, pos.y, - true, 1 << ev.button); + true, 1 << mappedButton); break; case 'mouseup': this._handleMouseButton(pos.x, pos.y, - false, 1 << ev.button); + false, 1 << mappedButton); break; case 'mousemove': this._handleMouseMove(pos.x, pos.y);