7 Commits

Author SHA1 Message Date
6201cf5b0c feat: update dynamic library description 2024-07-24 19:19:20 +02:00
aaafadf732 fix: pid/vid wrong 2024-07-24 19:07:18 +02:00
fe80867ce4 feat: M4G support 2024-07-24 18:28:47 +02:00
72a8e084ce fix: plugins can't execute plugins 2024-07-16 15:21:34 +02:00
989e844190 fix: compound order 2024-07-11 13:40:31 +02:00
500221f39a feat: experimental support for compounds 2024-07-11 13:38:19 +02:00
Raymond Li
d91273d27b Update CONTRIBUTING.md 2024-07-10 00:22:40 +02:00
11 changed files with 138 additions and 31 deletions

View File

@@ -29,7 +29,7 @@ You may need to run through some additional setup to get Rust running inside Int
- Python >=3.10
- Rust Stable (For Tauri Development)
I know, python in JS projects is extremely annoying, unfortunately,
I know, python in JS projects is extremely annoying. Unfortunately,
it seems to be the only platform that offers a functional
way to subset variable woff2 fonts with ligatures.

View File

@@ -104,6 +104,23 @@ actions:
<<: *tertiary_keymap
id: "KM_3_R"
variant: right
558:
id: HOLD_COMPOUND
title: Dynamic Library
icon: layers
description: |
Allows for the activation & creation of dynamic chord libraries.
When included as part of a chord output,
that chord's input becomes the seed for a dynamic chord library,
and that library is activated.
Any new chords created while a dynamic library is active are established one level above its seed.
559:
id: RELEASE_COMPOUND
title: Base Library
icon: layers_clear
description: |
Re-activates your base chord library,
and deactivates any currently active dynamic chord library.
576:
id: ACTION_DELAY_1000
icon: clock_loader_90

View File

@@ -0,0 +1,37 @@
name: M4G
col:
# Ring / Middle
- offset: [2, 0]
row:
- switch: { d: 25, e: 26, n: 27, w: 28, s: 29 }
- switch: { d: 20, e: 21, n: 22, w: 23, s: 24 }
- offset: [4, 0]
switch: { d: 65, w: 66, n: 67, e: 68, s: 69 }
- switch: { d: 70, w: 71, n: 72, e: 73, s: 74 }
- offset: [2, 0]
row:
- switch: { d: 40, e: 41, n: 42, w: 43, s: 44 }
- switch: { d: 35, e: 36, n: 37, w: 38, s: 39 }
- offset: [4, 0]
switch: { d: 80, w: 81, n: 82, e: 83, s: 84 }
- switch: { d: 85, w: 86, n: 87, e: 88, s: 89 }
# Pinkie / Index
- offset: [0, -3]
row:
- switch: { d: 30, e: 31, n: 32, w: 33, s: 34 }
- offset: [4, 0]
switch: { d: 15, e: 16, n: 17, w: 18, s: 19 }
- switch: { d: 60, w: 61, n: 62, e: 63, s: 64 }
- offset: [4, 0]
switch: { d: 75, w: 76, n: 77, e: 78, s: 79 }
# Thumbs
- row:
- offset: [5.5, 0.5]
switch: { d: 10, e: 11, n: 12, w: 13, s: 14 }
- offset: [1, 0.5]
switch: { d: 55, w: 56, n: 57, e: 58, s: 59 }
- row:
- offset: [4.5, -0.25]
switch: { d: 5, e: 6, n: 7, w: 8, s: 9 }
- offset: [3, -0.25]
switch: { d: 50, w: 51, n: 52, e: 53, s: 54 }

View File

@@ -33,6 +33,10 @@
import("$lib/assets/layouts/generic/103-key.yml").then(
(it) => it.default as VisualLayout,
),
M4G: () =>
import("$lib/assets/layouts/m4g.yml").then(
(it) => it.default as VisualLayout,
),
};
</script>

View File

@@ -55,3 +55,19 @@ export function deserializeActions(native: bigint): number[] {
return actions;
}
/**
* Hashes a chord input the same way as CCOS
*/
export function hashChord(actions: number[]) {
const chord = new Uint8Array(16);
const view = new DataView(chord.buffer);
const serialized = serializeActions(actions);
view.setBigUint64(0, serialized & 0xffff_ffff_ffff_ffffn, true);
view.setBigUint64(8, serialized >> 64n, true);
let hash = 2166136261;
for (let i = 0; i < 16; i++) {
hash = Math.imul(hash ^ view.getUint8(i), 16777619);
}
return hash & 0x3fff_ffff;
}

View File

@@ -16,6 +16,7 @@ const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }],
["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }],
["X", { usbProductId: 33163, usbVendorId: 12346 }],
["M4G S3", { usbProductId: 4097, usbVendorId: 12346 }],
]);
const KEY_COUNTS = {
@@ -23,6 +24,7 @@ const KEY_COUNTS = {
TWO: 90,
LITE: 67,
X: 256,
M4G: 90,
} as const;
if (
@@ -88,8 +90,8 @@ export class CharaDevice {
private suspendDebounceId?: number;
version!: SemVer;
company!: "CHARACHORDER";
device!: "ONE" | "TWO" | "LITE" | "X";
company!: "CHARACHORDER" | "FORGE";
device!: "ONE" | "TWO" | "LITE" | "X" | "M4G";
chipset!: "M0" | "S2" | "S3";
keyCount!: 90 | 67 | 256;
@@ -126,9 +128,9 @@ export class CharaDevice {
await this.send(1, "VERSION").then(([version]) => version),
);
const [company, device, chipset] = await this.send(3, "ID");
this.company = company as "CHARACHORDER";
this.device = device as "ONE" | "TWO" | "LITE" | "X";
this.chipset = chipset as "M0" | "S2" | "S3";
this.company = company as typeof this.company;
this.device = device as typeof this.device;
this.chipset = chipset as typeof this.chipset;
this.keyCount = KEY_COUNTS[this.device];
} catch (e) {
alert(e);

View File

@@ -1,6 +1,6 @@
import { persistentWritable } from "$lib/storage";
import { derived } from "svelte/store";
import type { Chord } from "$lib/serial/chord";
import { hashChord, type Chord } from "$lib/serial/chord";
import {
deviceChords,
deviceLayout,
@@ -158,3 +158,9 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
a.localeCompare(b),
);
});
export const chordHashes = derived(
chords,
(chords) =>
new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)),
);

View File

@@ -144,7 +144,6 @@
progress = i;
if ("phrase" in chord) {
console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout));
}
}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { ChordInfo } from "$lib/undo-redo";
import { changes, ChangeType } from "$lib/undo-redo";
import { changes, chordHashes, ChangeType } from "$lib/undo-redo";
import { createEventDispatcher } from "svelte";
import LL from "$i18n/i18n-svelte";
import ActionString from "$lib/components/ActionString.svelte";
@@ -8,6 +8,7 @@
import { serialPort } from "$lib/serial/connection";
import { get } from "svelte/store";
import { inputToAction } from "./input-converter";
import { hashChord } from "$lib/serial/chord";
export let chord: ChordInfo | undefined = undefined;
@@ -21,14 +22,15 @@
}
function makeChordInput(...actions: number[]) {
const compound = compoundIndices ?? [];
const compound = compoundInputs[0]
? hashChord(compoundInputs[0].actions)
: 0;
return [
...compound,
...Array.from(
{
length: 12 - (compound.length + actions.length + 1),
length: 12 - actions.length,
},
() => 0,
(_, i) => (compound >> (i * 10)) & 0x3ff,
),
...actions.toSorted(compare),
];
@@ -73,7 +75,6 @@
function addSpecial(event: MouseEvent) {
selectAction(event, (action) => {
changes.update((changes) => {
console.log(compoundIndices, chordActions, action);
changes.push({
type: ChangeType.Chord,
id: chord!.id,
@@ -85,10 +86,30 @@
});
}
function* resolveCompound(chord?: ChordInfo) {
if (!chord) return;
let current = chord;
for (let i = 0; i < 10; i++) {
if (current.actions[3] !== 0) return;
const compound = current.actions
.slice(0, 3)
.reduce((a, b, i) => a | (b << (i * 10)));
if (compound === 0) return;
const next = $chordHashes.get(compound);
if (!next) {
return null;
}
current = next;
yield next;
}
return;
}
$: chordActions = chord?.actions
.slice(chord.actions.lastIndexOf(0) + 1)
.toSorted(compare);
$: compoundIndices = chord?.actions.slice(0, chord.actions.indexOf(0));
$: compoundInputs = [...resolveCompound(chord)].reverse();
</script>
<button
@@ -110,12 +131,15 @@
<span>{$LL.configure.chords.NEW_CHORD()}</span>
{/if}
{#if !editing}
{#each compoundIndices ?? [] as index}
<sub>{index}</sub>
{/each}
{#if compoundIndices?.length}
{#each compoundInputs as compound}
<sub
><ActionString
display="keys"
actions={compound.actions.slice(compound.actions.lastIndexOf(0) + 1)}
></ActionString>
</sub>
<span>&rarr;</span>
{/if}
{/each}
{/if}
<ActionString
display="keys"

View File

@@ -137,9 +137,6 @@
if (parent) {
}
}
console.log(nodeBefore);
console.log(context);
return null;
},
});

View File

@@ -5,6 +5,8 @@
let resolveRequest: ((data: unknown) => void) | undefined = undefined;
let source: MessageEventSource | undefined = undefined;
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
async function post(channel: string, args: unknown[]) {
while (ongoingRequest) {
await ongoingRequest;
@@ -35,15 +37,18 @@
),
);
new Function("Action", "Chara", event.data.script)(
Action,
Object.fromEntries(
event.data.charaChannels.map((name) => [
name,
(...args: unknown[]) => post(name, args),
]),
),
const Chara = Object.fromEntries(
event.data.charaChannels.map((name) => [
name,
(...args: unknown[]) => post(name, args),
]),
);
AsyncFunction(
"Action",
"Chara",
'"use strict"\n' + event.data.script,
)(Action, Chara);
}
}
</script>