mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-21 17:32:41 +00:00
Compare commits
7 Commits
v1.5.2
...
6201cf5b0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
6201cf5b0c
|
|||
|
aaafadf732
|
|||
|
fe80867ce4
|
|||
|
72a8e084ce
|
|||
|
989e844190
|
|||
|
500221f39a
|
|||
|
|
d91273d27b |
@@ -29,7 +29,7 @@ You may need to run through some additional setup to get Rust running inside Int
|
|||||||
- Python >=3.10
|
- Python >=3.10
|
||||||
- Rust Stable (For Tauri Development)
|
- 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
|
it seems to be the only platform that offers a functional
|
||||||
way to subset variable woff2 fonts with ligatures.
|
way to subset variable woff2 fonts with ligatures.
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,23 @@ actions:
|
|||||||
<<: *tertiary_keymap
|
<<: *tertiary_keymap
|
||||||
id: "KM_3_R"
|
id: "KM_3_R"
|
||||||
variant: right
|
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:
|
576:
|
||||||
id: ACTION_DELAY_1000
|
id: ACTION_DELAY_1000
|
||||||
icon: clock_loader_90
|
icon: clock_loader_90
|
||||||
|
|||||||
37
src/lib/assets/layouts/m4g.yml
Normal file
37
src/lib/assets/layouts/m4g.yml
Normal 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 }
|
||||||
@@ -33,6 +33,10 @@
|
|||||||
import("$lib/assets/layouts/generic/103-key.yml").then(
|
import("$lib/assets/layouts/generic/103-key.yml").then(
|
||||||
(it) => it.default as VisualLayout,
|
(it) => it.default as VisualLayout,
|
||||||
),
|
),
|
||||||
|
M4G: () =>
|
||||||
|
import("$lib/assets/layouts/m4g.yml").then(
|
||||||
|
(it) => it.default as VisualLayout,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -55,3 +55,19 @@ export function deserializeActions(native: bigint): number[] {
|
|||||||
|
|
||||||
return actions;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
|
|||||||
["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }],
|
["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }],
|
||||||
["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }],
|
["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }],
|
||||||
["X", { usbProductId: 33163, usbVendorId: 12346 }],
|
["X", { usbProductId: 33163, usbVendorId: 12346 }],
|
||||||
|
["M4G S3", { usbProductId: 4097, usbVendorId: 12346 }],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const KEY_COUNTS = {
|
const KEY_COUNTS = {
|
||||||
@@ -23,6 +24,7 @@ const KEY_COUNTS = {
|
|||||||
TWO: 90,
|
TWO: 90,
|
||||||
LITE: 67,
|
LITE: 67,
|
||||||
X: 256,
|
X: 256,
|
||||||
|
M4G: 90,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -88,8 +90,8 @@ export class CharaDevice {
|
|||||||
private suspendDebounceId?: number;
|
private suspendDebounceId?: number;
|
||||||
|
|
||||||
version!: SemVer;
|
version!: SemVer;
|
||||||
company!: "CHARACHORDER";
|
company!: "CHARACHORDER" | "FORGE";
|
||||||
device!: "ONE" | "TWO" | "LITE" | "X";
|
device!: "ONE" | "TWO" | "LITE" | "X" | "M4G";
|
||||||
chipset!: "M0" | "S2" | "S3";
|
chipset!: "M0" | "S2" | "S3";
|
||||||
keyCount!: 90 | 67 | 256;
|
keyCount!: 90 | 67 | 256;
|
||||||
|
|
||||||
@@ -126,9 +128,9 @@ export class CharaDevice {
|
|||||||
await this.send(1, "VERSION").then(([version]) => version),
|
await this.send(1, "VERSION").then(([version]) => version),
|
||||||
);
|
);
|
||||||
const [company, device, chipset] = await this.send(3, "ID");
|
const [company, device, chipset] = await this.send(3, "ID");
|
||||||
this.company = company as "CHARACHORDER";
|
this.company = company as typeof this.company;
|
||||||
this.device = device as "ONE" | "TWO" | "LITE" | "X";
|
this.device = device as typeof this.device;
|
||||||
this.chipset = chipset as "M0" | "S2" | "S3";
|
this.chipset = chipset as typeof this.chipset;
|
||||||
this.keyCount = KEY_COUNTS[this.device];
|
this.keyCount = KEY_COUNTS[this.device];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e);
|
alert(e);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { persistentWritable } from "$lib/storage";
|
import { persistentWritable } from "$lib/storage";
|
||||||
import { derived } from "svelte/store";
|
import { derived } from "svelte/store";
|
||||||
import type { Chord } from "$lib/serial/chord";
|
import { hashChord, type Chord } from "$lib/serial/chord";
|
||||||
import {
|
import {
|
||||||
deviceChords,
|
deviceChords,
|
||||||
deviceLayout,
|
deviceLayout,
|
||||||
@@ -158,3 +158,9 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
|
|||||||
a.localeCompare(b),
|
a.localeCompare(b),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const chordHashes = derived(
|
||||||
|
chords,
|
||||||
|
(chords) =>
|
||||||
|
new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)),
|
||||||
|
);
|
||||||
|
|||||||
@@ -144,7 +144,6 @@
|
|||||||
progress = i;
|
progress = i;
|
||||||
|
|
||||||
if ("phrase" in chord) {
|
if ("phrase" in chord) {
|
||||||
console.log(encodeChord(chord, osLayout));
|
|
||||||
await index.addAsync(i, encodeChord(chord, osLayout));
|
await index.addAsync(i, encodeChord(chord, osLayout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ChordInfo } from "$lib/undo-redo";
|
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 { createEventDispatcher } from "svelte";
|
||||||
import LL from "$i18n/i18n-svelte";
|
import LL from "$i18n/i18n-svelte";
|
||||||
import ActionString from "$lib/components/ActionString.svelte";
|
import ActionString from "$lib/components/ActionString.svelte";
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
import { serialPort } from "$lib/serial/connection";
|
import { serialPort } from "$lib/serial/connection";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { inputToAction } from "./input-converter";
|
import { inputToAction } from "./input-converter";
|
||||||
|
import { hashChord } from "$lib/serial/chord";
|
||||||
|
|
||||||
export let chord: ChordInfo | undefined = undefined;
|
export let chord: ChordInfo | undefined = undefined;
|
||||||
|
|
||||||
@@ -21,14 +22,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeChordInput(...actions: number[]) {
|
function makeChordInput(...actions: number[]) {
|
||||||
const compound = compoundIndices ?? [];
|
const compound = compoundInputs[0]
|
||||||
|
? hashChord(compoundInputs[0].actions)
|
||||||
|
: 0;
|
||||||
return [
|
return [
|
||||||
...compound,
|
|
||||||
...Array.from(
|
...Array.from(
|
||||||
{
|
{
|
||||||
length: 12 - (compound.length + actions.length + 1),
|
length: 12 - actions.length,
|
||||||
},
|
},
|
||||||
() => 0,
|
(_, i) => (compound >> (i * 10)) & 0x3ff,
|
||||||
),
|
),
|
||||||
...actions.toSorted(compare),
|
...actions.toSorted(compare),
|
||||||
];
|
];
|
||||||
@@ -73,7 +75,6 @@
|
|||||||
function addSpecial(event: MouseEvent) {
|
function addSpecial(event: MouseEvent) {
|
||||||
selectAction(event, (action) => {
|
selectAction(event, (action) => {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
console.log(compoundIndices, chordActions, action);
|
|
||||||
changes.push({
|
changes.push({
|
||||||
type: ChangeType.Chord,
|
type: ChangeType.Chord,
|
||||||
id: chord!.id,
|
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
|
$: chordActions = chord?.actions
|
||||||
.slice(chord.actions.lastIndexOf(0) + 1)
|
.slice(chord.actions.lastIndexOf(0) + 1)
|
||||||
.toSorted(compare);
|
.toSorted(compare);
|
||||||
$: compoundIndices = chord?.actions.slice(0, chord.actions.indexOf(0));
|
$: compoundInputs = [...resolveCompound(chord)].reverse();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -110,12 +131,15 @@
|
|||||||
<span>{$LL.configure.chords.NEW_CHORD()}</span>
|
<span>{$LL.configure.chords.NEW_CHORD()}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !editing}
|
{#if !editing}
|
||||||
{#each compoundIndices ?? [] as index}
|
{#each compoundInputs as compound}
|
||||||
<sub>{index}</sub>
|
<sub
|
||||||
{/each}
|
><ActionString
|
||||||
{#if compoundIndices?.length}
|
display="keys"
|
||||||
|
actions={compound.actions.slice(compound.actions.lastIndexOf(0) + 1)}
|
||||||
|
></ActionString>
|
||||||
|
</sub>
|
||||||
<span>→</span>
|
<span>→</span>
|
||||||
{/if}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
<ActionString
|
<ActionString
|
||||||
display="keys"
|
display="keys"
|
||||||
|
|||||||
@@ -137,9 +137,6 @@
|
|||||||
if (parent) {
|
if (parent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(nodeBefore);
|
|
||||||
|
|
||||||
console.log(context);
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
let resolveRequest: ((data: unknown) => void) | undefined = undefined;
|
let resolveRequest: ((data: unknown) => void) | undefined = undefined;
|
||||||
let source: MessageEventSource | undefined = undefined;
|
let source: MessageEventSource | undefined = undefined;
|
||||||
|
|
||||||
|
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||||
|
|
||||||
async function post(channel: string, args: unknown[]) {
|
async function post(channel: string, args: unknown[]) {
|
||||||
while (ongoingRequest) {
|
while (ongoingRequest) {
|
||||||
await ongoingRequest;
|
await ongoingRequest;
|
||||||
@@ -35,15 +37,18 @@
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
new Function("Action", "Chara", event.data.script)(
|
const Chara = Object.fromEntries(
|
||||||
Action,
|
event.data.charaChannels.map((name) => [
|
||||||
Object.fromEntries(
|
name,
|
||||||
event.data.charaChannels.map((name) => [
|
(...args: unknown[]) => post(name, args),
|
||||||
name,
|
]),
|
||||||
(...args: unknown[]) => post(name, args),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AsyncFunction(
|
||||||
|
"Action",
|
||||||
|
"Chara",
|
||||||
|
'"use strict"\n' + event.data.script,
|
||||||
|
)(Action, Chara);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user