From f076bd7a7a400d04d5d69ae41130c5bb94fa8fb1 Mon Sep 17 00:00:00 2001 From: John Nathaniel Calvara Date: Sun, 19 Apr 2026 05:46:09 +0800 Subject: [PATCH 1/3] added Monsgeek Support (Specifically Fun60Ultra TMR) --- AnalogSense.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/AnalogSense.js b/AnalogSense.js index c90c240..eab70d6 100644 --- a/AnalogSense.js +++ b/AnalogSense.js @@ -911,6 +911,39 @@ class AsProviderBytech extends AsProvider } } +// needs web driver or software driver open and Simulation turned on via Config > Magnetic Switch Settings to register the keys. +class AsProviderMonsgeek extends AsProvider { + static populateFilters(filters) { + // Fun60Ultra (TMR Variant) + filters.push({ vendorId: 0x3151, productId: 0x5030, usagePage: 12, usage: 1 }); + } + + startListening(handler) { + const _this = this; + this.buffer = {}; + + this.dev.oninputreport = function (event) { + const raw = event.data.getUint16(2); + const key = raw & 0x00FF; + const analog = event.data.getUint16(1, true); + const analogHigh = raw >> 8; + + const scancode = analogsense.monsgeekScancodeToHidScancode(key); + if (analog === 0) { + delete _this.buffer[scancode]; + } else { + _this.buffer[scancode] = analog / 340; + } + + handler(_this._bufferToActiveKeys()); + } + }; + + stopListening() { + this.dev.oninputreport = undefined; + } +}; + window.analogsense = { providers: [ AsProviderWootingV1, @@ -922,6 +955,7 @@ window.analogsense = { AsProviderKeychron, AsProviderMadlions, AsProviderBytech, + AsProviderMonsgeek, ], findProviderForDevice: function(dev) { @@ -1034,7 +1068,7 @@ window.analogsense = { { return wooting_to_name[scancode]; } - return String(Number(scancode)); + return Number.isFinite(Number(scancode)) ? String(Number(scancode)) : String(scancode); }, razerScancodeToHidScancode: function(scancode) { @@ -1157,5 +1191,22 @@ window.analogsense = { } console.warn("Failed to map DrunkDeer key to HID scancode:", i); return 0; - } + }, + monsgeekScancodeToHidScancode(scancode) + { + //Fun60Ultra TMR Variant keymap + const monsgeek_keys = { + 0x0001: "Escape", 0x0007: "1",0x000d: "2",0x0013: "3",0x0019: "4",0x001f: "5",0x0025: "6",0x002b: "7",0x0031: "8",0x0037: "9",0x003d: "0",0x0043: "-",0x0049: "=",0x004f: "Backspace", + 0x0002: "Tab", 0x0008: "Q",0x000e: "W",0x0014: "E",0x001a: "R",0x0020: "T",0x0026: "Y",0x002c: "U",0x0050: "I",0x0038: "O",0x003e: "P",0x0044: "[",0x004a: "]",0x0032: "Backslash", + 0x0003: "Caps Lock", 0x0009: "A",0x000f: "S",0x0015: "D",0x001b: "F",0x0021: "G",0x0027: "H",0x002d: "J",0x0033: "K",0x0039: "L",0x003f: ";",0x0045: "'",0x0051: "Enter", + 0x0004: "Left Shift", 0x0010: "Z",0x0016: "X",0x001c: "C",0x0022: "V",0x0028: "B",0x002e: "N",0x0034: "M",0x003a: ",",0x0040: ".",0x0046: "/",0x004c: "Right Shift", + 0x0005: "Left Ctrl", 0x0011: "Left Meta", 0x0017: "Left Alt", 0x0029: "Space", 0x003b: "Right Alt", 0x0041: "Fn", 0x0047: "Context Menu", 0x004d: "Right Ctrl", + }; + + if (scancode in monsgeek_keys) { + return monsgeek_keys[scancode]; + } + console.warn("Failed to map Monsgeek key to HID scancode:", scancode); + return 0; + } }; \ No newline at end of file From 917bd443b02a5ccc7d943c6bee5050a44ddba7c1 Mon Sep 17 00:00:00 2001 From: John Nathaniel Calvara Date: Mon, 20 Apr 2026 00:53:25 +0800 Subject: [PATCH 2/3] Improve Monsgeek Support --- AnalogSense.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/AnalogSense.js b/AnalogSense.js index eab70d6..e4f90ea 100644 --- a/AnalogSense.js +++ b/AnalogSense.js @@ -911,18 +911,47 @@ class AsProviderBytech extends AsProvider } } -// needs web driver or software driver open and Simulation turned on via Config > Magnetic Switch Settings to register the keys. class AsProviderMonsgeek extends AsProvider { + constructor(dev) + { + super(dev); + + if (dev.collections?.some(c => c.usagePage === 65535 && c.usage === 2)) + { + this.controlDev = dev; + } + else + { + this.inputDev = dev; + } + } + static populateFilters(filters) { // Fun60Ultra (TMR Variant) - filters.push({ vendorId: 0x3151, productId: 0x5030, usagePage: 12, usage: 1 }); + filters.push({ vendorId: 0x3151, productId: 0x5030, usagePage: 0xFFFF }); // input stream; specifically, usagePage: 12, usage: 1 + filters.push({ vendorId: 0x3151, productId: 0x5030, usagePage: 0xFFFF, usage: 2 }); // control interface; specifically, usagePage: 65535 } + async _setSimulationMode(on = true) { + const buf = new Uint8Array(65); + buf[0] = 0x1b; + buf[1] = on ? 0x01 : 0x00; + buf[2] = on ? 0xe3 : 0xe4; + + try{ + await this.controlDev.sendFeatureReport(0, buf) + } catch (err) { + console.error("Error sending feature report on usagePage:" + this.controlDev.collections[0].usagePage + " usage:" + this.controlDev.collections[0].usage, err); + } + } + startListening(handler) { const _this = this; this.buffer = {}; - this.dev.oninputreport = function (event) { + this._setSimulationMode(true); + + this.inputDev.oninputreport = function (event) { const raw = event.data.getUint16(2); const key = raw & 0x00FF; const analog = event.data.getUint16(1, true); @@ -940,7 +969,8 @@ class AsProviderMonsgeek extends AsProvider { }; stopListening() { - this.dev.oninputreport = undefined; + this._setSimulationMode(false); + this.inputDev.oninputreport = undefined; } }; @@ -1026,6 +1056,23 @@ window.analogsense = { if (!dev.opened) { await dev.open(); + } + if (provider === AsProviderMonsgeek) + { + let existing = result.find(p => p instanceof AsProviderMonsgeek); + + if (existing) + { + if (dev.collections?.some(c => c.usagePage === 65535 && c.usage === 2)) + { + existing.controlDev = dev; + } + else + { + existing.inputDev = dev; + } + continue; + } } result.push(new provider(dev)); } From 0f884bdc59d96e496200806170289d737668e912 Mon Sep 17 00:00:00 2001 From: John Nathaniel Calvara Date: Mon, 20 Apr 2026 02:13:11 +0800 Subject: [PATCH 3/3] fix failed initialization on first instance on Monsgeek Fun60Ultra TMR. --- AnalogSense.js | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/AnalogSense.js b/AnalogSense.js index e4f90ea..a8fd563 100644 --- a/AnalogSense.js +++ b/AnalogSense.js @@ -938,11 +938,7 @@ class AsProviderMonsgeek extends AsProvider { buf[1] = on ? 0x01 : 0x00; buf[2] = on ? 0xe3 : 0xe4; - try{ - await this.controlDev.sendFeatureReport(0, buf) - } catch (err) { - console.error("Error sending feature report on usagePage:" + this.controlDev.collections[0].usagePage + " usage:" + this.controlDev.collections[0].usage, err); - } + await this.controlDev.sendFeatureReport(0, buf).catch(console.error); } startListening(handler) { @@ -1057,7 +1053,7 @@ window.analogsense = { { await dev.open(); } - if (provider === AsProviderMonsgeek) + if (provider === AsProviderMonsgeek) { let existing = result.find(p => p instanceof AsProviderMonsgeek); @@ -1090,6 +1086,7 @@ window.analogsense = { provider.populateFilters(filters); } const devices = await navigator.hid.requestDevice({ filters }); + const monsgeekDevs = []; for (const dev of devices) { const provider = analogsense.findProviderForDevice(dev); @@ -1099,8 +1096,31 @@ window.analogsense = { { await dev.open(); } - return new provider(dev); + if (provider === AsProviderMonsgeek) + { + monsgeekDevs.push(dev); + } + else + { + return new provider(dev); + } + } + } + if (monsgeekDevs.length > 0) + { + const provider = new AsProviderMonsgeek(monsgeekDevs[0]); + for (const dev of monsgeekDevs) + { + if (dev.collections?.some(c => c.usagePage === 65535 && c.usage === 2)) + { + provider.controlDev = dev; + } + else + { + provider.inputDev = dev; + } } + return provider; } } else