diff --git a/icons.config.js b/icons.config.js index 2ae7ef79..877f4746 100644 --- a/icons.config.js +++ b/icons.config.js @@ -57,6 +57,7 @@ const config = { "graphic_eq", "mail", "calculate", + "playground_2", "open_in_browser", "chevron_backward", "chevron_forward", diff --git a/src/lib/ccos/attachment.ts b/src/lib/ccos/attachment.ts index 74bf87c4..c392d4fe 100644 --- a/src/lib/ccos/attachment.ts +++ b/src/lib/ccos/attachment.ts @@ -1,26 +1,35 @@ import type { Attachment } from "svelte/attachments"; -import { browser } from "$app/environment"; -import { persistentWritable } from "$lib/storage"; +import type { CharaDevice } from "$lib/serial/device"; +import type { CCOS, CCOSKeyboardEvent } from "./ccos"; +import type { ReplayRecorder } from "$lib/charrecorder/core/recorder"; -export const emulatedCCOS = persistentWritable("emulatedCCOS", false); - -export function ccosKeyInterceptor() { - return ((element: Window) => { - const ccos = browser - ? import("./ccos").then((module) => module.fetchCCOS(".test")) - : Promise.resolve(undefined); +export function ccosKeyInterceptor( + port: CharaDevice | undefined, + recorder: ReplayRecorder, +) { + return ((element: HTMLElement) => { + const ccos = + port?.port && "handleKeyEvent" in port?.port + ? (port.port as CCOS) + : undefined; + console.log("Attaching CCOS key interceptor", ccos); function onEvent(event: KeyboardEvent) { - ccos.then((it) => it?.handleKeyEvent(event)); + ccos?.handleKeyEvent(event); + if (!event.defaultPrevented) { + recorder.next(event); + } } - element.addEventListener("keydown", onEvent, true); - element.addEventListener("keyup", onEvent, true); + if (ccos) { + element.addEventListener("keydown", onEvent, true); + element.addEventListener("keyup", onEvent, true); + element.add; + } return () => { - ccos.then((it) => it?.destroy()); element.removeEventListener("keydown", onEvent, true); element.removeEventListener("keyup", onEvent, true); }; - }) satisfies Attachment; + }) satisfies Attachment; } diff --git a/src/lib/ccos/ccos.ts b/src/lib/ccos/ccos.ts index c9dce154..b2fd06de 100644 --- a/src/lib/ccos/ccos.ts +++ b/src/lib/ccos/ccos.ts @@ -1,7 +1,6 @@ import { getMeta } from "$lib/meta/meta-storage"; import type { SerialPortLike } from "$lib/serial/device"; import type { - CCOSInEvent, CCOSInitEvent, CCOSKeyPressEvent, CCOSKeyReleaseEvent, @@ -11,7 +10,7 @@ import { KEYCODE_TO_SCANCODE, SCANCODE_TO_KEYCODE } from "./ccos-interop"; const device = "zero_wasm"; -class CCOSKeyboardEvent extends KeyboardEvent { +export class CCOSKeyboardEvent extends KeyboardEvent { constructor(...params: ConstructorParameters) { super(...params); } @@ -26,7 +25,46 @@ const MASK_GUI = 0b1000_1000; export class CCOS implements SerialPortLike { private readonly currKeys = new Set(); - private readonly layout = new Map(); + private readonly layout = new Map([ + ...Array.from( + { length: 26 }, + (_, i) => + [ + JSON.stringify([`Key${String.fromCharCode(65 + i)}`, "Shift"]), + String.fromCharCode(65 + i), + ] as const, + ), + ...Array.from( + { length: 10 }, + (_, i) => [JSON.stringify([`Key${i}`]), i.toString()] as const, + ), + + [JSON.stringify(["Space"]), " "], + [JSON.stringify(["Backquote"]), "`"], + [JSON.stringify(["Minus"]), "-"], + [JSON.stringify(["Comma"]), ","], + [JSON.stringify(["Period"]), "."], + [JSON.stringify(["Semicolon"]), ";"], + [JSON.stringify(["Equal"]), "="], + + [JSON.stringify(["Backquote", "Shift"]), "~"], + [JSON.stringify(["Minus", "Shift"]), "_"], + [JSON.stringify(["Comma", "Shift"]), "<"], + [JSON.stringify(["Period", "Shift"]), ">"], + [JSON.stringify(["Semicolon", "Shift"]), ":"], + [JSON.stringify(["Equal", "Shift"]), "+"], + + [JSON.stringify(["Digit0", "Shift"]), ")"], + [JSON.stringify(["Digit1", "Shift"]), "!"], + [JSON.stringify(["Digit2", "Shift"]), "@"], + [JSON.stringify(["Digit3", "Shift"]), "#"], + [JSON.stringify(["Digit4", "Shift"]), "$"], + [JSON.stringify(["Digit5", "Shift"]), "%"], + [JSON.stringify(["Digit6", "Shift"]), "^"], + [JSON.stringify(["Digit7", "Shift"]), "&"], + [JSON.stringify(["Digit8", "Shift"]), "*"], + [JSON.stringify(["Digit9", "Shift"]), "("], + ]); private readonly worker = new Worker("/ccos-worker.js", { type: "module" }); @@ -126,7 +164,6 @@ export class CCOS implements SerialPortLike { this.controller?.enqueue(event.data); return; } - console.log("CCOS worker message", event.data); switch (event.data.type) { case "ready": { this.resolveReady(); @@ -220,7 +257,7 @@ export class CCOS implements SerialPortLike { } export async function fetchCCOS( - version = ".2.2.0-beta.12+266bdda", + version = "3.0.0-rc.0", fetch: typeof window.fetch = window.fetch, ): Promise { const meta = await getMeta(device, version, fetch); diff --git a/src/lib/serial/device.ts b/src/lib/serial/device.ts index 323faff5..2a3f0330 100644 --- a/src/lib/serial/device.ts +++ b/src/lib/serial/device.ts @@ -147,7 +147,7 @@ export class CharaDevice { version!: string; company!: "CHARACHORDER" | "FORGE"; device!: "ONE" | "TWO" | "LITE" | "X" | "M4G" | "ENGINE" | "ZERO"; - chipset!: "M0" | "S2" | "S3"; + chipset!: "M0" | "S2" | "S3" | "WASM"; keyCount!: 90 | 67 | 256; layerCount = 3; profileCount = 1; @@ -157,7 +157,7 @@ export class CharaDevice { } constructor( - private readonly port: SerialPortLike, + readonly port: SerialPortLike, public baudRate = 115200, ) {} diff --git a/src/routes/(app)/ConnectPopup.svelte b/src/routes/(app)/ConnectPopup.svelte index 8bc98f00..1e8b24e8 100644 --- a/src/routes/(app)/ConnectPopup.svelte +++ b/src/routes/(app)/ConnectPopup.svelte @@ -46,15 +46,6 @@ element?.closest("[popover]")?.hidePopover(); } - async function connectCC0(event: MouseEvent) { - const { fetchCCOS } = await import("$lib/ccos/ccos"); - closePopover(); - const ccos = await fetchCCOS(); - if (ccos) { - connect(ccos, !event.shiftKey); - } - } - async function connectDevice(event: MouseEvent) { const port = await navigator.serial.requestPort({ filters: event.shiftKey ? [] : [...PORT_FILTERS.values()], diff --git a/src/routes/(app)/Footer.svelte b/src/routes/(app)/Footer.svelte index 35f98cc9..40313265 100644 --- a/src/routes/(app)/Footer.svelte +++ b/src/routes/(app)/Footer.svelte @@ -116,6 +116,25 @@ {/if}
    +
  • + + + + Discord +
  • bug_report Bugs Editor -
    -

    Editor

    +

    + CCOS Emulator + {#if $serialPort?.chipset === "WASM"} + (Emulator Active) + {:else} + + {/if} +

    + +

    + Try a (limited) demo of CCOS running directly in your browser.
    Chording requires an NKRO Keyboard to work properly. +
    Browsers usually report key timings with limited accuracy to revent + fingerprinting, which can impact chording. +
    Results may vary. +
    + Use sidebar tabs to configure Chords, + Layout + and Settings. +

    {#if replay}
    @@ -66,7 +110,9 @@ {#key recorder}
    @@ -95,15 +141,38 @@ width: 100%; } + a { + display: inline; + padding: 0; + color: var(--md-sys-color-primary); + } + + small { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--md-sys-color-primary); + font-weight: 500; + font-size: 0.6em; + } + + button.primary { + display: inline-flex; + background: none; + color: var(--md-sys-color-primary); + } + .replay, .editor { - position: absolute; - top: 3em; - left: 0; transition: opacity 0.1s; + margin: 4px; + outline: 1px solid var(--md-sys-color-outline); padding: 16px; padding-bottom: 5em; - padding-left: 0; + + &:focus-within { + outline: 2px solid var(--md-sys-color-primary); + } } .toolbar { diff --git a/vite.config.ts b/vite.config.ts index b190339d..bc00f0ca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,6 +26,7 @@ process.env["VITE_LATEST_FIRMWARE"] = "1.1.4"; process.env["VITE_STORE_URL"] = "https://www.charachorder.com/"; process.env["VITE_MATRIX_URL"] = "https://charachorder.io/"; process.env["VITE_FIRMWARE_URL"] = "https://charachorder.io/firmware"; +process.env["VITE_DISCORD_URL"] = "https://discord.gg/CharaChorder"; export default defineConfig({ build: {