diff --git a/package-lock.json b/package-lock.json index 59b4c21f..8d7de614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "openmouse", "version": "0.1.0", "dependencies": { - "@openmouse/protocol": "https://github.com/OpenMouse-Project/mouse-protocol.git", + "@openmouse/protocol": "github:OpenMouse-Project/mouse-protocol#c10ba29", "preact": "^10.29.8" }, "devDependencies": { @@ -460,7 +460,7 @@ }, "node_modules/@openmouse/protocol": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/OpenMouse-Project/mouse-protocol.git#1d55f756abf0d1a897ca4a72d6b4832e38007d4e", + "resolved": "git+ssh://git@github.com/OpenMouse-Project/mouse-protocol.git#c10ba29b6ff5ef927f2cfa79d2bdb31243a26787", "engines": { "node": ">=20" } diff --git a/package.json b/package.json index 59d572ef..351ba6ae 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "preview": "vite preview" }, "dependencies": { - "@openmouse/protocol": "https://github.com/OpenMouse-Project/mouse-protocol.git", + "@openmouse/protocol": "github:OpenMouse-Project/mouse-protocol#c10ba29", "preact": "^10.29.8" }, "devDependencies": { diff --git a/public/devices/README.md b/public/devices/README.md index 2dd6e8b5..60d28188 100644 --- a/public/devices/README.md +++ b/public/devices/README.md @@ -71,5 +71,10 @@ a public release package. for Zaunkoenig M3K and is also used for the M2K entry. Confirm redistribution terms before including it in a public release package. +`attackshark-r5-ultra.png` was supplied from a Discord attachment URL as a +transparent render of the Attack Shark R5 Ultra and was cropped to its content +bounds and downscaled. Confirm redistribution terms before including it in a +public release package. + `razer-viper.webp` was supplied from a Best Buy shopping page for the device. Confirm redistribution terms before including it in a public release package. diff --git a/public/devices/attackshark-r5-ultra.png b/public/devices/attackshark-r5-ultra.png new file mode 100644 index 00000000..3082069d Binary files /dev/null and b/public/devices/attackshark-r5-ultra.png differ diff --git a/src/app/App.tsx b/src/app/App.tsx index 06e69b3d..a23de570 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -143,8 +143,9 @@ function Workspace({ show(has.pulsarPro, ["profiles"]) ? : null, ].filter((node) => node !== null); + const lightingZones = status.lightingZones?.length ? status.lightingZones : status.lighting ? [status.lighting] : []; const lighting = show(has.lighting, ["lighting"]) - ? [] + ? [] : []; const showProfiles = show(has.profiles, ["profiles"]); diff --git a/src/app/Profiles.tsx b/src/app/Profiles.tsx index 0feb697d..e4ae9f34 100644 --- a/src/app/Profiles.tsx +++ b/src/app/Profiles.tsx @@ -1,5 +1,6 @@ -import { useEffect, useRef, type CSSProperties, type ReactNode } from "react"; +import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react"; import { capabilitiesForFormat } from "@openmouse/protocol/drivers/logitech/onboard-profiles"; +import { LOGITECH_BUTTON_ACTIONS, type LogitechButtonAction } from "@openmouse/protocol/drivers/logitech/onboard-profiles"; import * as control from "../device/controller"; import type { ControlSnapshot } from "../device/types"; import { IconActivate, IconDisabled, IconEnabled, IconRefresh, IconRename, IconRunning, IconTrash } from "./icons"; @@ -38,7 +39,38 @@ const ICON_BUTTON_STYLE = (disabled: boolean, active = false): CSSProperties => opacity: disabled ? 0.4 : 1, }); +const SPECIAL_HID_KEYS: Readonly> = { + Enter: 0x28, Escape: 0x29, Backspace: 0x2a, Tab: 0x2b, Space: 0x2c, + Minus: 0x2d, Equal: 0x2e, BracketLeft: 0x2f, BracketRight: 0x30, + Backslash: 0x31, Semicolon: 0x33, Quote: 0x34, Backquote: 0x35, + Comma: 0x36, Period: 0x37, Slash: 0x38, CapsLock: 0x39, + PrintScreen: 0x46, ScrollLock: 0x47, Pause: 0x48, Insert: 0x49, + Home: 0x4a, PageUp: 0x4b, Delete: 0x4c, End: 0x4d, PageDown: 0x4e, + ArrowRight: 0x4f, ArrowLeft: 0x50, ArrowDown: 0x51, ArrowUp: 0x52, +}; + +function hidKeyForCode(code: string): number | null { + if (/^Key[A-Z]$/.test(code)) return 0x04 + code.charCodeAt(3) - 65; + if (/^Digit[1-9]$/.test(code)) return 0x1e + Number(code[5]) - 1; + if (code === "Digit0") return 0x27; + const functionKey = code.match(/^F([1-9]|1[0-2])$/); + if (functionKey) return 0x3a + Number(functionKey[1]) - 1; + return SPECIAL_HID_KEYS[code] ?? null; +} + +function shortcutLabel(event: KeyboardEvent): string { + return [event.ctrlKey ? "Ctrl" : null, event.shiftKey ? "Shift" : null, event.altKey ? "Alt" : null, + event.metaKey ? "Meta" : null, event.key.length === 1 ? event.key.toUpperCase() : event.key] + .filter(Boolean).join(" + "); +} + export function Profiles({ snapshot }: { snapshot: ControlSnapshot }): ReactNode { + const [assignmentLayer, setAssignmentLayer] = useState<"primary" | "g-shift">("primary"); + const [shortcutTarget, setShortcutTarget] = useState<{ layer: "primary" | "g-shift"; button: number } | null>(null); + const [shortcutSteps, setShortcutSteps] = useState>([]); + const [shortcutRecording, setShortcutRecording] = useState(false); + const [shortcutError, setShortcutError] = useState(""); + const lastShortcutAt = useRef(0); const inner = useRef(null); const body = useRef(null); const profiles = snapshot.onboardProfiles; @@ -56,6 +88,9 @@ export function Profiles({ snapshot }: { snapshot: ControlSnapshot }): ReactNode const contentsWritable = format?.writable === true; const nameLimit = format ? capabilitiesForFormat(format.id).maxNameLength : null; const hostTags = [hostRunning ? "active" : null, hostOpened ? "editing" : null].filter(Boolean).join(" · "); + const openedProfile = typeof snapshot.editedProfile === "number" + ? profiles?.find((profile) => profile.sector === snapshot.editedProfile) ?? null + : null; return (
)} + {openedProfile && contentsWritable ? ( +
+
+

BUTTONS

Onboard assignments

Choose what each physical control does in this profile.
+
+ + +
+
+
+ {(assignmentLayer === "primary" ? openedProfile.buttonAssignments : openedProfile.gShiftAssignments).map((assignment) => { + const buttonNames = ["Left click", "Right click", "Wheel click", "Back", "Forward", "DPI Shift", "DPI up", "DPI down"]; + return ( +