diff --git a/src/behaviors/HidUsagePicker.tsx b/src/behaviors/HidUsagePicker.tsx index 4cbf8566..a55478e4 100644 --- a/src/behaviors/HidUsagePicker.tsx +++ b/src/behaviors/HidUsagePicker.tsx @@ -2,23 +2,21 @@ import { Button, Checkbox, CheckboxGroup, - Collection, ComboBox, - Header, Input, - Key, Label, ListBox, ListBoxItem, Popover, - Section, + Tab, + TabList, + TabPanel, + Tabs, } from "react-aria-components"; -import { - hid_usage_from_page_and_id, - hid_usage_page_get_ids, -} from "../hid-usages"; +import { hid_usage_page_get_ids, hid_usage_get_metadata } from "../hid-usages"; import { useCallback, useMemo } from "react"; import { ChevronDown } from "lucide-react"; + export interface HidUsagePage { id: number; @@ -33,41 +31,6 @@ export interface HidUsagePickerProps { onValueChanged: (value?: number) => void; } -type UsageSectionProps = HidUsagePage; - -const UsageSection = ({ id, min, max }: UsageSectionProps) => { - const info = useMemo(() => hid_usage_page_get_ids(id), [id]); - - let usages = useMemo(() => { - let usages = info?.UsageIds || []; - if (max || min) { - usages = usages.filter( - (i) => - (i.Id <= (max || Number.MAX_SAFE_INTEGER) && i.Id >= (min || 0)) || - (id === 7 && i.Id >= 0xe0 && i.Id <= 0xe7) - ); - } - - return usages; - }, [id, min, max, info]); - - return ( -
-
{info?.Name}
- - {(i) => ( - - {i.Name} - - )} - -
- ); -}; - enum Mods { LeftControl = 0x01, LeftShift = 0x02, @@ -109,6 +72,156 @@ function mask_mods(value: number) { return value & ~(mods_to_flags(all_mods) << 24); } +const HidUsageGrid = ({ + value, + onValueChanged, + usagePages, +}: HidUsagePickerProps) => { + type Usage = { + Name: string; + Id: number; + pageName: string; + pageId: number; + }; + const allUsages = useMemo(() => { + return usagePages.flatMap((page) => { + const pageInfo = hid_usage_page_get_ids(page.id); + if (!pageInfo) { + return []; + } + + let usages = pageInfo.UsageIds || []; + if (page.max || page.min) { + usages = usages.filter( + (i) => + (i.Id <= (page.max || Number.MAX_SAFE_INTEGER) && + i.Id >= (page.min || 0)) || + (page.id === 7 && i.Id >= 0xe0 && i.Id <= 0xe7) + ); + } + + return usages.map((usage) => ({ + ...usage, + pageId: page.id, + pageName: pageInfo.Name, + })); + }); + }, [usagePages]); + + const selectedKey = value !== undefined ? mask_mods(value) : null; + + const getButtonLabel = (usage: Usage) => { + const metadata = hid_usage_get_metadata(usage.pageId, usage.Id); + if (metadata?.med) { + return metadata.med; + } + if (metadata?.short) { + return metadata.short; + } + + if (usage.pageName === "Keyboard/Keypad") { + const match = usage.Name.match(/^(Keyboard|Keypad) (\S+)/); + if (match && match[2]) { + return match[2]; + } + } + return usage.Name; + }; + + const categorizedUsages = useMemo(() => { + const categories: Record = {}; + + for (const usage of allUsages) { + const metadata = hid_usage_get_metadata(usage.pageId, usage.Id); + const category = metadata?.category || "Other"; + + if (!categories[category]) { + categories[category] = []; + } + categories[category].push(usage); + } + + return categories; + }, [allUsages]); + + const categoryOrder = ["Basic", "Numpad", "Apps/Media/Special", "International", "Other"]; + const sortedCategories = Object.keys(categorizedUsages).sort((a, b) => { + const indexA = categoryOrder.indexOf(a); + const indexB = categoryOrder.indexOf(b); + if (indexA !== -1 && indexB !== -1) return indexA - indexB; + if (indexA !== -1) return -1; + if (indexB !== -1) return 1; + return a.localeCompare(b); + }); + + return ( + + + {sortedCategories.map((category) => ( + + {category} + + ))} + + {sortedCategories.map((category) => ( + + {category === "Other" ? ( + + key !== null && onValueChanged(key as number) + } + > + +
+ + +
+ + + {(item: Usage) => { + const usageValue = (item.pageId << 16) | item.Id; + return ( + + {item.Name} + + ); + }} + + +
+ ) : ( + categorizedUsages[category].map((usage) => { + const usageValue = (usage.pageId << 16) | usage.Id; + return ( + + ); + }) + )} +
+ ))} +
+ ); +}; + export const HidUsagePicker = ({ label, value, @@ -122,7 +235,7 @@ export const HidUsagePicker = ({ }, [value]); const selectionChanged = useCallback( - (e: Key | null) => { + (e: number | undefined) => { let value = typeof e == "number" ? e : undefined; if (value !== undefined) { let mod_flags = mods_to_flags(mods.map((m) => parseInt(m))); @@ -148,45 +261,31 @@ export const HidUsagePicker = ({ ); return ( -
- {label && } - -
- - -
- - - {({ id, min, max }) => } - - -
- - {all_mods.map((m) => ( - - {mod_labels[m]} - - ))} - +
+
+ {label && } + + {all_mods.map((m) => ( + + {mod_labels[m]} + + ))} + +
+
); }; diff --git a/src/hid-usage-metadata.json b/src/hid-usage-metadata.json new file mode 100644 index 00000000..939929eb --- /dev/null +++ b/src/hid-usage-metadata.json @@ -0,0 +1,156 @@ +{ + "7": { + "4": { "category": "Basic" }, + "5": { "category": "Basic" }, + "6": { "category": "Basic" }, + "7": { "category": "Basic" }, + "8": { "category": "Basic" }, + "9": { "category": "Basic" }, + "10": { "category": "Basic" }, + "11": { "category": "Basic" }, + "12": { "category": "Basic" }, + "13": { "category": "Basic" }, + "14": { "category": "Basic" }, + "15": { "category": "Basic" }, + "16": { "category": "Basic" }, + "17": { "category": "Basic" }, + "18": { "category": "Basic" }, + "19": { "category": "Basic" }, + "20": { "category": "Basic" }, + "21": { "category": "Basic" }, + "22": { "category": "Basic" }, + "23": { "category": "Basic" }, + "24": { "category": "Basic" }, + "25": { "category": "Basic" }, + "26": { "category": "Basic" }, + "27": { "category": "Basic" }, + "28": { "category": "Basic" }, + "29": { "category": "Basic" }, + "30": { "short": "1 !", "category": "Basic" }, + "31": { "short": "2 @", "category": "Basic" }, + "32": { "short": "3 #", "category": "Basic" }, + "33": { "short": "4 $", "category": "Basic" }, + "34": { "short": "5 %", "category": "Basic" }, + "35": { "short": "6 ^", "category": "Basic" }, + "36": { "short": "7 &", "category": "Basic" }, + "37": { "short": "8 *", "category": "Basic" }, + "38": { "short": "9 (", "category": "Basic" }, + "39": { "short": "0 )", "category": "Basic" }, + "40": { "short": "Ret", "med": "Return", "category": "Basic" }, + "41": { "short": "Esc", "long": "Escape", "category": "Basic" }, + "42": { "short": "BkSp", "med": "BkSpc", "long": "Backspace", "category": "Basic" }, + "43": { "category": "Basic" }, + "44": { "short": "␣", "med": "Space", "category": "Basic" }, + "45": { "short": "- _", "med": "Dash", "category": "Basic" }, + "46": { "short": "= +", "med": "Equals", "category": "Basic" }, + "47": { "short": "[ {", "category": "Basic" }, + "48": { "short": "] }", "category": "Basic" }, + "49": { "short": "\\ |", "category": "Basic" }, + "50": { "short": "NUHS", "long": "NonUS Hash", "category": "International" }, + "51": { "short": "; :", "category": "Basic" }, + "52": { "short": "' \"", "category": "Basic" }, + "53": { "short": "` ~", "category": "Basic" }, + "54": { "short": ", <", "category": "Basic" }, + "55": { "short": ". >", "category": "Basic" }, + "56": { "short": "/ ?", "category": "Basic" }, + "57": { "short": "Cap", "med": "CapsLk", "long": "Caps Lock", "category": "Basic" }, + "58": { "category": "Basic" }, + "59": { "category": "Basic" }, + "60": { "category": "Basic" }, + "61": { "category": "Basic" }, + "62": { "category": "Basic" }, + "63": { "category": "Basic" }, + "64": { "category": "Basic" }, + "65": { "category": "Basic" }, + "66": { "category": "Basic" }, + "67": { "category": "Basic" }, + "68": { "category": "Basic" }, + "69": { "category": "Basic" }, + "70": { "short": "PrSc", "long": "Print Scr", "category": "Basic" }, + "71": { "short": "ScLk", "long": "ScrollLock", "category": "Basic" }, + "72": { "short": "Paus", "med": "Pause", "category": "Basic" }, + "73": { "short": "Ins", "med": "Insert", "category": "Basic" }, + "74": { "category": "Basic" }, + "75": { "short": "PgUp", "med": "PageUp", "long": "Page Up", "category": "Basic" }, + "76": { "short": "Del", "med": "Delete", "category": "Basic" }, + "77": { "category": "Basic" }, + "78": { "short": "PgDn", "med": "PageDn", "long": "Page Down", "category": "Basic" }, + "79": { "short": "→", "category": "Basic" }, + "80": { "short": "←", "category": "Basic" }, + "81": { "short": "↓", "category": "Basic" }, + "82": { "short": "↑", "category": "Basic" }, + "83": { "short": "Num", "med": "NumLck", "long": "Num Lock", "category": "Numpad" }, + "84": { "short": "/", "category": "Numpad" }, + "85": { "short": "*", "category": "Numpad" }, + "86": { "short": "-", "category": "Numpad" }, + "87": { "short": "+", "category": "Numpad" }, + "88": { "short": "Ent", "med": "KP Ent", "long": "KP Enter", "category": "Numpad" }, + "89": { "short": "1 En", "med": "1 End", "category": "Numpad" }, + "90": { "short": "2 ↓", "category": "Numpad" }, + "91": { "short": "3 PD", "med": "3 PgDn", "category": "Numpad" }, + "92": { "short": "4 ←", "category": "Numpad" }, + "93": { "short": "5", "category": "Numpad" }, + "94": { "short": "6 →", "category": "Numpad" }, + "95": { "short": "7 Hm", "med": "7 Home", "category": "Numpad" }, + "96": { "short": "8 ↑", "category": "Numpad" }, + "97": { "short": "9 PU", "med": "9 PgUp", "category": "Numpad" }, + "98": { "short": "0 In", "med": "0 Ins", "long": "0 Insert", "category": "Numpad" }, + "99": { "short": ". Dl", "med": ". Del", "long": ". Delete", "category": "Numpad" }, + "101": { "short": "Menu", "med": "Menu", "long": "Applicat'n (Menu)", "category": "Basic" }, + "102": { "short": "Power", "med": "Power", "category": "Apps/Media/Special" }, + "100": { "short": "NUBS", "category": "International" }, + "103": { "short": "=", "category": "Numpad" }, + "104": { "category": "Apps/Media/Special" }, + "105": { "category": "Apps/Media/Special" }, + "106": { "category": "Apps/Media/Special" }, + "107": { "category": "Apps/Media/Special" }, + "108": { "category": "Apps/Media/Special" }, + "109": { "category": "Apps/Media/Special" }, + "110": { "category": "Apps/Media/Special" }, + "111": { "category": "Apps/Media/Special" }, + "112": { "category": "Apps/Media/Special" }, + "113": { "category": "Apps/Media/Special" }, + "114": { "category": "Apps/Media/Special" }, + "115": { "category": "Apps/Media/Special" }, + "118": { "category": "Apps/Media/Special" }, + "133": { "short": ",", "category": "Numpad" }, + "135": { "short": "Intl1", "med": "Int1 ろ", "category": "International" }, + "136": { "short": "Intl2", "med": "Int2 かな", "category": "International" }, + "137": { "short": "Intl3", "med": "Int3 ¥","category": "International" }, + "138": { "short": "Intl4", "med": "Int4 変換","category": "International" }, + "139": { "short": "Intl5", "med": "Int5 無変換","category": "International" }, + "140": { "short": "Intl6", "med": "Int6 ,","category": "International" }, + "141": { "short": "Intl7", "category": "International" }, + "142": { "short": "Intl8", "category": "International" }, + "143": { "short": "Intl9", "category": "International" }, + "144": { "short": "Lang1", "med": "Lang1 한/영", "category": "International" }, + "145": { "short": "Lang2", "med": "Lang2 한자", "category": "International" }, + "146": { "short": "Lang3", "med": "Lang3 カタカナ", "category": "International" }, + "147": { "short": "Lang4", "med": "Lang4 ひらがな", "category": "International" }, + "148": { "short": "Lang5", "med": "Lang5 半角/全角", "category": "International" }, + "149": { "short": "Lang6", "category": "International" }, + "150": { "short": "Lang7", "category": "International" }, + "151": { "short": "Lang8", "category": "International" }, + "152": { "short": "Lang9", "category": "International" }, + "176": { "short": "00", "category": "Numpad" }, + "177": { "short": "000" }, + "224": { "short": "Ctrl", "med": "L Ctrl", "category": "Basic" }, + "225": { "short": "Shft", "med": "L Shft", "long": "L Shift", "category": "Basic" }, + "226": { "short": "Alt", "med": "L Alt", "long": "Left Alt", "category": "Basic" }, + "227": { "short": "GUI", "med": "L GUI", "long": "Left GUI", "category": "Basic" }, + "228": { "short": "Ctrl", "med": "R Ctrl", "category": "Basic" }, + "229": { "short": "Shft", "med": "R Shft", "long": "R Shift", "category": "Basic" }, + "230": { "short": "AltG", "med": "AltGr", "category": "Basic" }, + "231": { "short": "GUI", "med": "R GUI", "long": "Right GUI", "category": "Basic" } + }, + "12": { + "111": { "short": "🔆", "category": "Apps/Media/Special" }, + "112": { "short": "🔅", "category": "Apps/Media/Special" }, + "181": { "short": "⇥", "category": "Apps/Media/Special" }, + "182": { "short": "⇤", "category": "Apps/Media/Special" }, + "205": { "short": "⏯️", "category": "Apps/Media/Special" }, + "226": { "short": "🔇", "category": "Apps/Media/Special" }, + "233": { "short": "🔊", "category": "Apps/Media/Special" }, + "234": { "short": "🔉", "category": "Apps/Media/Special" } + } +} diff --git a/src/hid-usage-name-overrides.json b/src/hid-usage-name-overrides.json deleted file mode 100644 index f2773ff5..00000000 --- a/src/hid-usage-name-overrides.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "7": { - "30": { "short": "1" }, - "31": { "short": "2" }, - "32": { "short": "3" }, - "33": { "short": "4" }, - "34": { "short": "5" }, - "35": { "short": "6" }, - "36": { "short": "7" }, - "37": { "short": "8" }, - "38": { "short": "9" }, - "39": { "short": "0" }, - "40": { "short": "Ret", "med": "Return" }, - "41": { "short": "Esc", "long": "Escape" }, - "42": { "short": "BkSp", "med": "BkSpc", "long": "Backspace" }, - "44": { "short": "␣", "med": "Space" }, - "45": { "short": "-", "med": "Dash" }, - "46": { "short": "=", "med": "Equals" }, - "47": { "short": "{" }, - "48": { "short": "}" }, - "49": { "short": "\\" }, - "50": { "short": "NUHS", "long": "NonUS Hash" }, - "51": { "short": ";" }, - "52": { "short": "'" }, - "53": { "short": "`" }, - "54": { "short": "," }, - "55": { "short": "." }, - "56": { "short": "/" }, - "57": { "short": "Cap", "long": "Caps Lock" }, - "70": { "short": "PrSc", "long": "Print Scr" }, - "71": { "short": "ScLk", "long": "ScrollLock" }, - "72": { "short": "Paus", "med": "Pause" }, - "73": { "short": "Ins", "med": "Insert" }, - "75": { "short": "PgUp", "med": "PageUp", "long": "Page Up" }, - "76": { "short": "Del", "med": "Delete" }, - "78": { "short": "PgDn", "med": "PageDn", "long": "Page Down" }, - "79": { "short": "→" }, - "80": { "short": "←" }, - "81": { "short": "↓" }, - "82": { "short": "↑" }, - "83": { "short": "Num", "med": "NumLck", "long": "Num Lock" }, - "84": { "short": "/" }, - "85": { "short": "*" }, - "86": { "short": "-" }, - "87": { "short": "+" }, - "88": { "short": "Ent", "med": "KP Ent", "long": "KP Enter" }, - "89": { "short": "1 En", "med": "1 End" }, - "90": { "short": "2 ↓" }, - "91": { "short": "3 PD", "med": "3 PgDn" }, - "92": { "short": "4 ←" }, - "93": { "short": "5" }, - "94": { "short": "6 →" }, - "95": { "short": "7 Hm", "med": "7 Home" }, - "96": { "short": "8 ↑" }, - "97": { "short": "9 PU", "med": "9 PgUp" }, - "98": { "short": "0 In", "med": "0 Ins", "long": "0 Insert" }, - "99": { "short": ". Dl", "med": ". Del", "long": ". Delete" }, - "101": { "short": "Appl", "med": "Appl", "long": "Applicat'n" }, - "102": { "short": "Power", "med": "Power" }, - "100": { "short": "NUBS" }, - "103": { "short": "=" }, - "133": { "short": "," }, - "134": { "short": "=" }, - "176": { "short": "00" }, - "177": { "short": "000" }, - "224": { "short": "Ctrl", "med": "L Ctrl" }, - "225": { "short": "Shft", "med": "L Shft", "long": "L Shift" }, - "226": { "short": "Alt", "med": "L Alt", "long": "Left Alt" }, - "227": { "short": "GUI", "med": "L GUI", "long": "Left GUI" }, - "228": { "short": "Ctrl", "med": "R Ctrl" }, - "229": { "short": "Shft", "med": "R Shft", "long": "R Shift" }, - "230": { "short": "AltG", "med": "AltGr" }, - "231": { "short": "GUI", "med": "R GUI", "long": "Right GUI" } - }, - "12": { - "111": { "short": "🔆" }, - "112": { "short": "🔅" }, - "181": { "short": "⇥" }, - "182": { "short": "⇤" }, - "205": { "short": "⏯️" }, - "226": { "short": "🔇" }, - "233": { "short": "🔊" }, - "234": { "short": "🔉" } - } -} diff --git a/src/hid-usages.ts b/src/hid-usages.ts index a3e3dd84..4cc25985 100644 --- a/src/hid-usages.ts +++ b/src/hid-usages.ts @@ -1,15 +1,16 @@ // import { UsagePages } from "./HidUsageTables-1.5.json"; // Filtered with `cat src/HidUsageTables-1.5.json | jq '{ UsagePages: [.UsagePages[] | select([.Id] |inside([7, 12]))] }' > src/keyboard-and-consumer-usage-tables.json` import { UsagePages } from "./keyboard-and-consumer-usage-tables.json"; -import HidOverrides from "./hid-usage-name-overrides.json"; +import HidSupplementaryMetadata from "./hid-usage-metadata.json"; -interface HidLabels { +interface HidMetadata { short?: string; med?: string; long?: string; + category?: string; } -const overrides: Record> = HidOverrides; +const overrides: Record> = HidSupplementaryMetadata; export interface UsageId { Id: number; @@ -41,12 +42,21 @@ export const hid_usage_get_label = ( (u) => u.Id === usage_id )?.Name; -export const hid_usage_get_labels = ( +export const hid_usage_get_metadata = ( usage_page: number, usage_id: number -): { short?: string; med?: string; long?: string } => - overrides[usage_page.toString()]?.[usage_id.toString()] || { - short: UsagePages.find((p) => p.Id === usage_page)?.UsageIds?.find( - (u) => u.Id === usage_id - )?.Name, - }; +): { short?: string; med?: string; long?: string, category?: string } => { + if(overrides[usage_page.toString()]?.[usage_id.toString()]?.short) { + return overrides[usage_page.toString()]?.[usage_id.toString()]; + } else { + const fullName = UsagePages.find((p) => p.Id === usage_page)?.UsageIds?.find( + (u) => u.Id === usage_id + )?.Name + return { + short: fullName?.replace(/^Keyboard /, ""), + med: fullName?.replace(/^Keyboard /, ""), + long: fullName, + category: overrides[usage_page.toString()]?.[usage_id.toString()]?.category || "Other" + } + } +} \ No newline at end of file diff --git a/src/keyboard/HidUsageLabel.tsx b/src/keyboard/HidUsageLabel.tsx index aa15195c..2ad05f52 100644 --- a/src/keyboard/HidUsageLabel.tsx +++ b/src/keyboard/HidUsageLabel.tsx @@ -1,5 +1,5 @@ import { - hid_usage_get_labels, + hid_usage_get_metadata, hid_usage_page_and_id_from_usage, } from "../hid-usages"; @@ -17,13 +17,13 @@ export const HidUsageLabel = ({ hid_usage }: HidUsageLabelProps) => { // TODO: Do something with implicit mods! page &= 0xff; - let labels = hid_usage_get_labels(page, id); + let labels = hid_usage_get_metadata(page, id); return (