From 177f9af6aa84b6d4e8bda62d6bffb9984eb2b8ce Mon Sep 17 00:00:00 2001 From: ydw1904 Date: Mon, 28 Sep 2026 00:40:01 +0800 Subject: [PATCH] fix(razer): explain the Basilisk V3 Pro in Bluetooth mode In Bluetooth mode the V3 Pro enumerates as 0x068E:0x00AC (Razer's Bluetooth SIG id) and takes settings over a vendor GATT service, not HID, so no driver can claim it and it never appeared in the picker or Bridge's device list. Offer it anyway, in both, and tell the user to switch to the HyperSpeed dongle or the cable when it is picked. Co-Authored-By: Claude Opus 5.5 --- src/bridge-hid.test.ts | 2 ++ src/bridge-hid.ts | 4 ++-- src/device/controller.ts | 10 ++++++++-- src/device/hid-filters.ts | 12 ++++++++++++ src/i18n-ar.ts | 1 + src/i18n-de.ts | 1 + src/i18n-es.ts | 1 + src/i18n-fr.ts | 1 + src/i18n-ja.ts | 1 + src/i18n-ko.ts | 1 + src/i18n-pt.ts | 1 + src/i18n-ru.ts | 1 + src/i18n-vi.ts | 1 + src/i18n-zh.ts | 1 + src/i18n.ts | 1 + 15 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/device/hid-filters.ts diff --git a/src/bridge-hid.test.ts b/src/bridge-hid.test.ts index 0a9e7c28..74c07301 100644 --- a/src/bridge-hid.test.ts +++ b/src/bridge-hid.test.ts @@ -39,6 +39,8 @@ test("drives a device end to end over the socket", async () => { const listing = hid.getDevices(); assert.equal(fake.sent[0].type, "list"); assert.ok(Array.isArray(fake.sent[0].vendorIds) && (fake.sent[0].vendorIds as number[]).length > 0); + // Razer's Bluetooth id, so a V3 Pro in Bluetooth mode can explain itself. + assert.ok((fake.sent[0].vendorIds as number[]).includes(0x068e)); fake.reply({ id: fake.sent[0].id, ok: true, devices: [MOUSE] }); const [device] = await listing; diff --git a/src/bridge-hid.ts b/src/bridge-hid.ts index 365583f4..cae0f7b8 100644 --- a/src/bridge-hid.ts +++ b/src/bridge-hid.ts @@ -18,7 +18,7 @@ // socket. Safari does not, and blocks it as mixed content — Safari needs // Bridge to serve the app itself over loopback, which is a separate change. -import { SUPPORTED_HID_FILTERS } from "@openmouse/protocol/drivers/vendors"; +import { HID_FILTERS } from "./device/hid-filters.ts"; import { describeHidDevice, markHidActivity } from "./hid-diagnostics.ts"; @@ -444,7 +444,7 @@ class BridgeHid implements HID { } async #listDevices(): Promise { - const reply = await this.#client.request({ type: "list", vendorIds: vendorIdsFor(SUPPORTED_HID_FILTERS) }); + const reply = await this.#client.request({ type: "list", vendorIds: vendorIdsFor(HID_FILTERS) }); const { devices, added, removed } = this.#client.reconcile(reply.devices ?? []); if (this.#enumerated) { for (const device of added) this.#emit("connect", device); diff --git a/src/device/controller.ts b/src/device/controller.ts index 054d21ed..f45e03c3 100644 --- a/src/device/controller.ts +++ b/src/device/controller.ts @@ -161,7 +161,8 @@ import { type KeychronNapeButtonAction, type KeychronNapeLayerKeymap, } from "@openmouse/protocol/keychron"; -import { SUPPORTED_HID_FILTERS, VENDOR_ID } from "@openmouse/protocol/drivers/vendors"; +import { VENDOR_ID } from "@openmouse/protocol/drivers/vendors"; +import { BASILISK_V3_PRO_BLUETOOTH, HID_FILTERS } from "./hid-filters"; import { WLMouseHidClient } from "@openmouse/protocol/drivers/wlmouse/hid"; import { MicrosoftHidClient } from "@openmouse/protocol/drivers/microsoft/hid"; import { DareuHidClient } from "@openmouse/protocol/drivers/dareu/hid"; @@ -2289,7 +2290,7 @@ function handleHidDisconnect(event: HIDConnectionEvent): void { async function requestSupportedClient(): Promise { if (!navigator.hid) throw new Error(st("ctl.noWebHid")); - const devices = await navigator.hid.requestDevice({ filters: SUPPORTED_HID_FILTERS }); + const devices = await navigator.hid.requestDevice({ filters: HID_FILTERS }); if (devices.length === 0) return null; if (devices.some((device) => eggWeIsSupported(device))) { @@ -2307,6 +2308,11 @@ async function requestSupportedClient(): Promise { return best.client; } + if (devices.some((device) => device.vendorId === BASILISK_V3_PRO_BLUETOOTH.vendorId + && device.productId === BASILISK_V3_PRO_BLUETOOTH.productId)) { + throw new Error(st("ctl.razerBluetooth")); + } + const details = devices.map((device) => describeHidDevice(device)).join(" · "); const nativeOnly = devices.find((device) => { const product = RAZER_PRODUCTS.get(device.productId) as { nativeOnly?: boolean } | undefined; diff --git a/src/device/hid-filters.ts b/src/device/hid-filters.ts new file mode 100644 index 00000000..8af367ad --- /dev/null +++ b/src/device/hid-filters.ts @@ -0,0 +1,12 @@ +import { SUPPORTED_HID_FILTERS } from "@openmouse/protocol/drivers/vendors"; + +/** + * The Basilisk V3 Pro in Bluetooth mode. It enumerates under Razer's Bluetooth + * SIG id rather than 0x1532, and takes settings over a vendor GATT service + * instead of HID, so no driver can claim it. It is offered anyway so picking + * it explains itself rather than the mouse silently never appearing. + */ +export const BASILISK_V3_PRO_BLUETOOTH = { vendorId: 0x068e, productId: 0x00ac }; + +/** Everything the app asks the browser (or Bridge) for. */ +export const HID_FILTERS: HIDDeviceFilter[] = [...SUPPORTED_HID_FILTERS, BASILISK_V3_PRO_BLUETOOTH]; diff --git a/src/i18n-ar.ts b/src/i18n-ar.ts index 62f12c2e..4cde6080 100644 --- a/src/i18n-ar.ts +++ b/src/i18n-ar.ts @@ -677,6 +677,7 @@ export const ar: Record = { "ctl.reconnecting": "جارٍ إعادة الاتصال", "ctl.readingPrev": "جارٍ قراءة الجهاز المصرّح به سابقًا.", "ctl.useAdd": "استخدم «إضافة جهاز» إن لم يُعد الماوس الاتصال تلقائيًا.", + "ctl.razerBluetooth": "لا يمكن ضبط Basilisk V3 Pro عبر البلوتوث بعد. حوّله إلى HyperSpeed (2.4 GHz) مع الدونجل، أو وصّل الكابل.", "ctl.couldNotReconnect": "تعذّرت إعادة اتصال الماوس", "ctl.dpiUnsupported": "قيمة DPI هذه غير مدعومة من هذا الماوس.", "ctl.closestStep": "لا يستطيع هذا الماوس تنفيذ {dpi} DPI. أقرب خطوة يدعمها هي {closest}.", diff --git a/src/i18n-de.ts b/src/i18n-de.ts index 7aa93016..81b7e6ce 100644 --- a/src/i18n-de.ts +++ b/src/i18n-de.ts @@ -678,6 +678,7 @@ export const de: Record = { "ctl.reconnecting": "Wird wiederverbunden", "ctl.readingPrev": "Das zuvor autorisierte Gerät wird gelesen.", "ctl.useAdd": "Nutze Gerät hinzufügen, falls sich die Maus nicht automatisch neu verbindet.", + "ctl.razerBluetooth": "Die Basilisk V3 Pro lässt sich über Bluetooth noch nicht einstellen. Stelle sie mit dem Dongle auf HyperSpeed (2,4 GHz) um oder schließe das Kabel an.", "ctl.couldNotReconnect": "Die Maus konnte nicht wiederverbunden werden", "ctl.dpiUnsupported": "Dieser DPI-Wert wird von dieser Maus nicht unterstützt.", "ctl.closestStep": "Diese Maus kann keine {dpi} DPI. Der nächstgelegene unterstützte Schritt ist {closest}.", diff --git a/src/i18n-es.ts b/src/i18n-es.ts index 4b2e7281..79004d20 100644 --- a/src/i18n-es.ts +++ b/src/i18n-es.ts @@ -678,6 +678,7 @@ export const es: Record = { "ctl.reconnecting": "Reconectando", "ctl.readingPrev": "Leyendo el dispositivo previamente autorizado.", "ctl.useAdd": "Usa Agregar dispositivo si el mouse no se reconecta automáticamente.", + "ctl.razerBluetooth": "El Basilisk V3 Pro aún no se puede configurar por Bluetooth. Cámbialo a HyperSpeed (2,4 GHz) con el dongle o conecta el cable.", "ctl.couldNotReconnect": "No se pudo reconectar el mouse", "ctl.dpiUnsupported": "Ese valor de DPI no es compatible con este mouse.", "ctl.closestStep": "Este mouse no puede hacer {dpi} DPI. El paso más cercano que admite es {closest}.", diff --git a/src/i18n-fr.ts b/src/i18n-fr.ts index fa29ef5d..675ca501 100644 --- a/src/i18n-fr.ts +++ b/src/i18n-fr.ts @@ -678,6 +678,7 @@ export const fr: Record = { "ctl.reconnecting": "Reconnexion", "ctl.readingPrev": "Lecture de l'appareil précédemment autorisé.", "ctl.useAdd": "Utilisez Ajouter un appareil si la souris ne se reconnecte pas automatiquement.", + "ctl.razerBluetooth": "La Basilisk V3 Pro ne peut pas encore être configurée en Bluetooth. Passez-la en HyperSpeed (2,4 GHz) avec le dongle, ou branchez le câble.", "ctl.couldNotReconnect": "Impossible de reconnecter la souris", "ctl.dpiUnsupported": "Cette valeur DPI n'est pas prise en charge par cette souris.", "ctl.closestStep": "Cette souris ne peut pas faire {dpi} DPI. Le pas le plus proche pris en charge est {closest}.", diff --git a/src/i18n-ja.ts b/src/i18n-ja.ts index 0b9d35b6..ccd5545d 100644 --- a/src/i18n-ja.ts +++ b/src/i18n-ja.ts @@ -678,6 +678,7 @@ export const ja: Record = { "ctl.reconnecting": "再接続中", "ctl.readingPrev": "以前に許可されたデバイスを読み取り中です。", "ctl.useAdd": "マウスが自動的に再接続しない場合は「デバイスを追加」を使用してください。", + "ctl.razerBluetooth": "Basilisk V3 Pro はまだ Bluetooth では設定できません。ドングルで HyperSpeed(2.4 GHz)に切り替えるか、ケーブルを接続してください。", "ctl.couldNotReconnect": "マウスを再接続できませんでした", "ctl.dpiUnsupported": "このDPI値はこのマウスでサポートされていません。", "ctl.closestStep": "このマウスは{dpi} DPIに対応していません。対応している最も近い値は{closest}です。", diff --git a/src/i18n-ko.ts b/src/i18n-ko.ts index 3b59959a..5fd86dd3 100644 --- a/src/i18n-ko.ts +++ b/src/i18n-ko.ts @@ -678,6 +678,7 @@ export const ko: Record = { "ctl.reconnecting": "재연결 중", "ctl.readingPrev": "이전에 승인된 장치를 읽는 중입니다.", "ctl.useAdd": "마우스가 자동으로 재연결되지 않으면 \"장치 추가\"를 사용하세요.", + "ctl.razerBluetooth": "Basilisk V3 Pro는 아직 블루투스로 설정할 수 없습니다. 동글을 사용해 HyperSpeed(2.4GHz)로 전환하거나 케이블을 연결하세요.", "ctl.couldNotReconnect": "마우스를 재연결할 수 없습니다", "ctl.dpiUnsupported": "이 DPI 값은 이 마우스에서 지원되지 않습니다.", "ctl.closestStep": "이 마우스는 {dpi} DPI를 지원하지 않습니다. 지원되는 가장 가까운 값은 {closest}입니다.", diff --git a/src/i18n-pt.ts b/src/i18n-pt.ts index 7ef466a2..a1ec7f9e 100644 --- a/src/i18n-pt.ts +++ b/src/i18n-pt.ts @@ -678,6 +678,7 @@ export const pt: Record = { "ctl.reconnecting": "Reconectando", "ctl.readingPrev": "Lendo o dispositivo autorizado anterior.", "ctl.useAdd": "Use Adicionar dispositivo se o mouse não reconectar sozinho.", + "ctl.razerBluetooth": "O Basilisk V3 Pro ainda não pode ser configurado por Bluetooth. Mude-o para HyperSpeed (2,4 GHz) com o dongle ou conecte o cabo.", "ctl.couldNotReconnect": "Não foi possível reconectar o mouse", "ctl.dpiUnsupported": "Este valor de DPI não é suportado por este mouse.", "ctl.closestStep": "Este mouse não faz {dpi} DPI. O passo mais próximo é {closest}.", diff --git a/src/i18n-ru.ts b/src/i18n-ru.ts index 5b0232d3..21142f75 100644 --- a/src/i18n-ru.ts +++ b/src/i18n-ru.ts @@ -677,6 +677,7 @@ export const ru: Record = { "ctl.reconnecting": "Переподключение", "ctl.readingPrev": "Читаю ранее разрешённое устройство.", "ctl.useAdd": "Нажмите «Добавить устройство», если мышь не подключилась сама.", + "ctl.razerBluetooth": "Basilisk V3 Pro пока нельзя настроить по Bluetooth. Переключите мышь в режим HyperSpeed (2,4 ГГц) с донглом или подключите кабель.", "ctl.couldNotReconnect": "Не удалось переподключить мышь", "ctl.dpiUnsupported": "Это значение DPI не поддерживается данной мышью.", "ctl.closestStep": "Эта мышь не умеет {dpi} DPI. Ближайший поддерживаемый шаг — {closest}.", diff --git a/src/i18n-vi.ts b/src/i18n-vi.ts index 0261042d..97dd0e60 100644 --- a/src/i18n-vi.ts +++ b/src/i18n-vi.ts @@ -626,6 +626,7 @@ export const vi: Record = { "ctl.reconnecting": "Đang kết nối lại", "ctl.readingPrev": "Đang đọc thiết bị đã được cấp quyền trước đó.", "ctl.useAdd": "Dùng Thêm thiết bị nếu chuột không tự động kết nối lại.", + "ctl.razerBluetooth": "Basilisk V3 Pro chưa thể cài đặt qua Bluetooth. Hãy chuyển sang HyperSpeed (2,4 GHz) với dongle hoặc cắm cáp.", "ctl.couldNotReconnect": "Không thể kết nối lại với chuột", "ctl.dpiUnsupported": "Giá trị DPI đó không được chuột này hỗ trợ.", "ctl.closestStep": "Chuột này không hỗ trợ {dpi} DPI. Mức gần nhất được hỗ trợ là {closest}.", diff --git a/src/i18n-zh.ts b/src/i18n-zh.ts index 46f3c9d6..f1eaaeeb 100644 --- a/src/i18n-zh.ts +++ b/src/i18n-zh.ts @@ -678,6 +678,7 @@ export const zh: Record = { "ctl.reconnecting": "重新连接中", "ctl.readingPrev": "正在读取此前已授权的设备。", "ctl.useAdd": "如果鼠标未能自动重连,请使用“添加设备”。", + "ctl.razerBluetooth": "Basilisk V3 Pro 暂不支持通过蓝牙设置。请使用接收器切换到 HyperSpeed(2.4 GHz)模式,或连接数据线。", "ctl.couldNotReconnect": "无法重新连接鼠标", "ctl.dpiUnsupported": "此鼠标不支持该 DPI 数值。", "ctl.closestStep": "此鼠标不支持 {dpi} DPI。最接近的支持挡位是 {closest}。", diff --git a/src/i18n.ts b/src/i18n.ts index 7fd1227d..47263068 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -681,6 +681,7 @@ const en = { "ctl.reconnecting": "Reconnecting", "ctl.readingPrev": "Reading the previously authorized device.", "ctl.useAdd": "Use Add device if the mouse does not reconnect automatically.", + "ctl.razerBluetooth": "The Basilisk V3 Pro can't be set up over Bluetooth yet. Switch it to HyperSpeed (2.4 GHz) with the dongle, or plug in the cable.", "ctl.couldNotReconnect": "Could not reconnect the mouse", "ctl.dpiUnsupported": "That DPI value is not supported by this mouse.", "ctl.closestStep": "This mouse cannot do {dpi} DPI. The closest step it supports is {closest}.",