Implement Phase 2: real platform shims + behavior driver + steno key defines
- zmk_key_shim: Key::Press/Release → raise_zmk_keycode_state_changed() Maps Javelin KeyCode to ZMK HID events (keyboard + consumer pages) - zmk_clock_shim: Clock:: → Zephyr k_uptime_get/k_cyc_to_us/k_msleep - zmk_flash_shim: Flash::EraseBlock/WriteBlock → Zephyr flash API - behavior_javelin_steno.c: proper DT_DRV_COMPAT, DT_INST_FOREACH_STATUS_OKAY - engine_init.cc: processor pipeline wiring (AllUp → JeffModifiers → Engine) - javelin_steno.dtsi: default &javsteno behavior instance - dt-bindings/zmk/javelin_steno.h: STENO_S1..STENO_ZR key defines for keymaps - Exclude thread.cc (pthreads), key.cc, clock.cc (replaced by shims) - Fix CMake: combine COMPILE_FLAGS + COMPILE_DEFINITIONS in single call Engine init deferred until dictionary loaded (Phase 3).
This commit is contained in:
parent
206e5231e1
commit
d4dc836b52
9 changed files with 213 additions and 73 deletions
|
|
@ -14,7 +14,6 @@ if(CONFIG_ZMK_JAVELIN_STENO)
|
|||
javelin/bit.cc
|
||||
javelin/button_script_manager.cc
|
||||
javelin/button_script.cc
|
||||
javelin/clock.cc
|
||||
javelin/console_input_buffer.cc
|
||||
javelin/console.cc
|
||||
javelin/container/cyclic_queue.cc
|
||||
|
|
@ -31,7 +30,6 @@ if(CONFIG_ZMK_JAVELIN_STENO)
|
|||
javelin/host_layout.cc
|
||||
javelin/key_code.cc
|
||||
javelin/key_press_parser.cc
|
||||
javelin/key.cc
|
||||
javelin/mem.cc
|
||||
javelin/orthography.cc
|
||||
javelin/pattern_component.cc
|
||||
|
|
@ -55,7 +53,6 @@ if(CONFIG_ZMK_JAVELIN_STENO)
|
|||
javelin/stroke_list_parser.cc
|
||||
javelin/stroke.cc
|
||||
javelin/system.cc
|
||||
javelin/thread.cc
|
||||
javelin/timer_manager.cc
|
||||
javelin/unicode_script.cc
|
||||
javelin/unicode.cc
|
||||
|
|
@ -182,13 +179,9 @@ if(CONFIG_ZMK_JAVELIN_STENO)
|
|||
${ZMK_INIT_SRC}
|
||||
)
|
||||
|
||||
# Set C++ compile flags for all Javelin + shim sources
|
||||
# C++ flags + Javelin compile definitions
|
||||
set_source_files_properties(${ALL_CXX_SRC} PROPERTIES
|
||||
COMPILE_FLAGS "-std=gnu++23 -fno-exceptions -fno-rtti -fno-threadsafe-statics"
|
||||
)
|
||||
|
||||
# Compile definitions for Javelin
|
||||
set_source_files_properties(${ALL_CXX_SRC} PROPERTIES
|
||||
COMPILE_DEFINITIONS "JAVELIN_USE_EMBEDDED_STENO=1;JAVELIN_USE_USER_DICTIONARY=1;JAVELIN_PLATFORM_ZEPHYR=1"
|
||||
)
|
||||
|
||||
|
|
|
|||
8
dts/behaviors/javelin_steno.dtsi
Normal file
8
dts/behaviors/javelin_steno.dtsi
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/ {
|
||||
behaviors {
|
||||
/omit-if-no-ref/ javsteno: javelin_steno {
|
||||
compatible = "zmk,behavior-javelin-steno";
|
||||
#binding-cells = <1>;
|
||||
};
|
||||
};
|
||||
};
|
||||
41
include/dt-bindings/zmk/javelin_steno.h
Normal file
41
include/dt-bindings/zmk/javelin_steno.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Steno key indices matching Javelin's StenoKey enum.
|
||||
// Use in keymap: &javsteno STENO_S1, &javsteno STENO_TL, etc.
|
||||
|
||||
#define STENO_S1 0
|
||||
#define STENO_S2 1
|
||||
#define STENO_TL 2
|
||||
#define STENO_KL 3
|
||||
#define STENO_PL 4
|
||||
#define STENO_WL 5
|
||||
#define STENO_HL 6
|
||||
#define STENO_RL 7
|
||||
#define STENO_A 8
|
||||
#define STENO_O 9
|
||||
#define STENO_STAR1 10
|
||||
#define STENO_STAR2 11
|
||||
#define STENO_STAR3 12
|
||||
#define STENO_STAR4 13
|
||||
#define STENO_E 14
|
||||
#define STENO_U 15
|
||||
#define STENO_FR 16
|
||||
#define STENO_RR 17
|
||||
#define STENO_PR 18
|
||||
#define STENO_BR 19
|
||||
#define STENO_LR 20
|
||||
#define STENO_GR 21
|
||||
#define STENO_TR 22
|
||||
#define STENO_SR 23
|
||||
#define STENO_DR 24
|
||||
#define STENO_ZR 25
|
||||
#define STENO_NUM1 26
|
||||
#define STENO_NUM2 27
|
||||
#define STENO_NUM3 28
|
||||
#define STENO_NUM4 29
|
||||
#define STENO_NUM5 30
|
||||
#define STENO_NUM6 31
|
||||
#define STENO_NUM7 32
|
||||
#define STENO_NUM8 33
|
||||
#define STENO_NUM9 34
|
||||
#define STENO_NUM10 35
|
||||
#define STENO_NUM11 36
|
||||
#define STENO_NUM12 37
|
||||
|
|
@ -9,4 +9,12 @@ void zmk_javelin_steno_process_key(int steno_key_index, bool is_press);
|
|||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
class StenoDictionary;
|
||||
struct StenoOrthography;
|
||||
|
||||
// Complete engine initialization with loaded dictionary.
|
||||
// Called from dictionary loading code once flash data is validated.
|
||||
void zmk_javelin_steno_init_engine(StenoDictionary &dictionary,
|
||||
const StenoOrthography &orthography);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,30 +1,26 @@
|
|||
#define DT_DRV_COMPAT zmk_behavior_javelin_steno
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <drivers/behavior.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/keymap.h>
|
||||
|
||||
#include "zmk_javelin_steno/zmk_platform_shim.h"
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
struct behavior_javelin_steno_config {};
|
||||
struct behavior_javelin_steno_data {};
|
||||
|
||||
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
int steno_key = binding->param1;
|
||||
zmk_javelin_steno_process_key(steno_key, true);
|
||||
return 0;
|
||||
zmk_javelin_steno_process_key((int)binding->param1, true);
|
||||
return ZMK_BEHAVIOR_OPAQUE;
|
||||
}
|
||||
|
||||
static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
int steno_key = binding->param1;
|
||||
zmk_javelin_steno_process_key(steno_key, false);
|
||||
return 0;
|
||||
zmk_javelin_steno_process_key((int)binding->param1, false);
|
||||
return ZMK_BEHAVIOR_OPAQUE;
|
||||
}
|
||||
|
||||
static int behavior_javelin_steno_init(const struct device *dev) {
|
||||
|
|
@ -33,16 +29,15 @@ static int behavior_javelin_steno_init(const struct device *dev) {
|
|||
}
|
||||
|
||||
static const struct behavior_driver_api behavior_javelin_steno_driver_api = {
|
||||
.locality = BEHAVIOR_LOCALITY_CENTRAL,
|
||||
.binding_pressed = on_keymap_binding_pressed,
|
||||
.binding_released = on_keymap_binding_released,
|
||||
.locality = BEHAVIOR_LOCALITY_CENTRAL,
|
||||
};
|
||||
|
||||
static struct behavior_javelin_steno_data behavior_javelin_steno_data;
|
||||
static struct behavior_javelin_steno_config behavior_javelin_steno_config;
|
||||
#define JAVSTENO_INST(n) \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, behavior_javelin_steno_init, NULL, \
|
||||
NULL, NULL, POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
||||
&behavior_javelin_steno_driver_api);
|
||||
|
||||
BEHAVIOR_DT_INST_DEFINE(0, behavior_javelin_steno_init, NULL,
|
||||
&behavior_javelin_steno_data,
|
||||
&behavior_javelin_steno_config,
|
||||
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
||||
&behavior_javelin_steno_driver_api);
|
||||
DT_INST_FOREACH_STATUS_OKAY(JAVSTENO_INST)
|
||||
|
|
|
|||
|
|
@ -1,38 +1,81 @@
|
|||
#include "zmk_javelin_steno/zmk_platform_shim.h"
|
||||
|
||||
#include "dictionary/dictionary_definition.h"
|
||||
#include "dictionary/dictionary_list.h"
|
||||
#include "dictionary/invalid_dictionary.h"
|
||||
#include "engine.h"
|
||||
#include "orthography.h"
|
||||
#include "processor/all_up.h"
|
||||
#include "processor/first_up.h"
|
||||
#include "processor/jeff_modifiers.h"
|
||||
#include "processor/processor.h"
|
||||
#include "processor/repeat.h"
|
||||
#include "steno_key_state.h"
|
||||
#include "static_allocate.h"
|
||||
#include "stroke.h"
|
||||
|
||||
// Static storage for the processor pipeline.
|
||||
// Pipeline: StenoProcessor → StenoAllUp → StenoEngine
|
||||
static StenoAllUp *allUp = nullptr;
|
||||
// Pipeline: StenoProcessor → StenoRepeat → StenoAllUp → StenoJeffModifiers → StenoEngine
|
||||
//
|
||||
// StenoProcessor converts raw StenoKey press/release → StenoKeyState.
|
||||
// StenoRepeat handles stroke repetition on held keys.
|
||||
// StenoAllUp triggers when all keys released (standard steno behavior).
|
||||
// StenoJeffModifiers handles modifier key combos within steno.
|
||||
// StenoEngine does dictionary lookup → text output via Key::Press/Release.
|
||||
|
||||
static JavelinStaticAllocate<StenoCompiledOrthography> compiledOrthography;
|
||||
static JavelinStaticAllocate<StenoJeffModifiers> jeffModifiers;
|
||||
static JavelinStaticAllocate<StenoRepeat> repeat;
|
||||
static JavelinStaticAllocate<StenoAllUp> allUp;
|
||||
static StenoProcessor *processor = nullptr;
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void zmk_javelin_steno_init(void) {
|
||||
// TODO Phase 2: Initialize with real dictionary and orthography.
|
||||
// For now this is a skeleton — engine construction requires a
|
||||
// StenoDictionaryCollection loaded from flash (Phase 3).
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use empty orthography until a dictionary collection is loaded (Phase 3).
|
||||
// The empty orthography has no rules — suffix folding won't work, but
|
||||
// basic dictionary lookups will function once a dict is in flash.
|
||||
new (compiledOrthography)
|
||||
StenoCompiledOrthography(StenoOrthography::emptyOrthography);
|
||||
|
||||
// TODO Phase 3: Load StenoDictionaryCollection from flash partition.
|
||||
// 1. Get pointer to steno_dict_partition start address
|
||||
// 2. Validate magic == 0x3443534a ('JSC4')
|
||||
// 3. Call collection->AddDictionariesToList() to populate dict list
|
||||
// 4. Construct engine with real dictionary
|
||||
//
|
||||
// Full init will look like:
|
||||
// 1. Read dictionary collection from flash partition
|
||||
// 2. Validate magic (0x3443534a = 'JSC4')
|
||||
// 3. Build dictionary list from collection
|
||||
// 4. Load compiled orthography
|
||||
// 5. Construct StenoEngine with dict + ortho
|
||||
// 6. Build processor pipeline: AllUp → Engine
|
||||
// 7. Wrap in StenoProcessor for key input
|
||||
// For now, engine construction is deferred until dictionary is available.
|
||||
// The processor pipeline is NOT built yet — process_key will early-return.
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void zmk_javelin_steno_process_key(int steno_key_index, bool is_press) {
|
||||
if (!processor) {
|
||||
return;
|
||||
}
|
||||
StenoKey key = static_cast<StenoKey>(steno_key_index);
|
||||
processor->Process(key, is_press);
|
||||
processor->Process(static_cast<StenoKey>(steno_key_index), is_press);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// Called once dictionary is loaded (Phase 3) to complete initialization.
|
||||
void zmk_javelin_steno_init_engine(StenoDictionary &dictionary,
|
||||
const StenoOrthography &orthography) {
|
||||
new (compiledOrthography) StenoCompiledOrthography(orthography);
|
||||
|
||||
new (StenoEngine::container)
|
||||
StenoEngine(dictionary, nullptr, compiledOrthography.value);
|
||||
|
||||
new (jeffModifiers) StenoJeffModifiers(StenoEngine::container.value);
|
||||
new (allUp) StenoAllUp(jeffModifiers.value);
|
||||
new (repeat) StenoRepeat(allUp.value);
|
||||
|
||||
static StenoProcessor processorInstance(repeat.value);
|
||||
processor = &processorInstance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
#include "clock.h"
|
||||
|
||||
// TODO Phase 2: Replace with Zephyr kernel time functions.
|
||||
// #include <zephyr/kernel.h>
|
||||
// uint32_t Clock::GetMilliseconds() { return k_uptime_get_32(); }
|
||||
// uint32_t Clock::GetMicroseconds() { return k_cyc_to_us_floor32(k_cycle_get_32()); }
|
||||
// void Clock::Sleep(uint32_t ms) { k_msleep(ms); }
|
||||
|
||||
uint32_t Clock::GetMilliseconds() {
|
||||
return 0;
|
||||
extern "C" {
|
||||
#include <zephyr/kernel.h>
|
||||
}
|
||||
|
||||
uint32_t Clock::GetMilliseconds() { return k_uptime_get_32(); }
|
||||
|
||||
uint32_t Clock::GetMicroseconds() {
|
||||
return 0;
|
||||
return k_cyc_to_us_floor32(k_cycle_get_32());
|
||||
}
|
||||
|
||||
void Clock::Sleep(uint32_t milliseconds) {
|
||||
(void)milliseconds;
|
||||
}
|
||||
void Clock::Sleep(uint32_t milliseconds) { k_msleep(milliseconds); }
|
||||
|
|
|
|||
|
|
@ -1,19 +1,46 @@
|
|||
#include "flash.h"
|
||||
#include "mem.h"
|
||||
|
||||
// TODO Phase 3: Replace with Zephyr flash API.
|
||||
// #include <zephyr/drivers/flash.h>
|
||||
// #include <zephyr/storage/flash_map.h>
|
||||
extern "C" {
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/flash.h>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
}
|
||||
|
||||
Flash Flash::instance;
|
||||
// Flash partition IDs — must match DTS overlay.
|
||||
// steno_dict_partition: compiled dictionary (read-only at runtime)
|
||||
// steno_user_partition: user dictionary (read-write)
|
||||
//
|
||||
// Javelin's Flash class uses raw pointers into XIP-mapped flash.
|
||||
// On nRF52840, internal flash is memory-mapped at 0x00000000.
|
||||
// We convert between XIP pointers and partition offsets.
|
||||
|
||||
#ifndef FIXED_PARTITION_ID
|
||||
#define FIXED_PARTITION_ID(label) FLASH_AREA_ID(label)
|
||||
#endif
|
||||
|
||||
// Flash::instance is defined in javelin/flash.cc (high-level methods).
|
||||
// We only provide the platform-specific EraseBlockInternal/WriteBlockInternal.
|
||||
|
||||
// Convert XIP address to flash device offset.
|
||||
// nRF52840 internal flash is at 0x00000000, so pointer == offset.
|
||||
static off_t xip_to_offset(const void *ptr) {
|
||||
return (off_t)(uintptr_t)ptr;
|
||||
}
|
||||
|
||||
void Flash::EraseBlockInternal(const void *target, size_t size) {
|
||||
(void)target;
|
||||
(void)size;
|
||||
const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash));
|
||||
if (!device_is_ready(dev)) {
|
||||
return;
|
||||
}
|
||||
flash_erase(dev, xip_to_offset(target), size);
|
||||
}
|
||||
|
||||
void Flash::WriteBlockInternal(const void *target, const void *data,
|
||||
size_t size) {
|
||||
(void)target;
|
||||
(void)data;
|
||||
(void)size;
|
||||
const struct device *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash));
|
||||
if (!device_is_ready(dev)) {
|
||||
return;
|
||||
}
|
||||
flash_write(dev, xip_to_offset(target), data, size);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,51 @@
|
|||
#include "key.h"
|
||||
#include "key_code.h"
|
||||
|
||||
// TODO Phase 2: Replace stubs with ZMK HID event emission.
|
||||
// Key::Press/Release are called by Javelin's output pipeline to send
|
||||
// translated text to the host. These need to map to
|
||||
// raise_zmk_keycode_state_changed() with correct usage pages.
|
||||
extern "C" {
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/keycode_state_changed.h>
|
||||
}
|
||||
|
||||
// Javelin KeyCode layout:
|
||||
// 0x04-0xE7 → USB HID Keyboard page (0x07)
|
||||
// 0xE0-0xE7 → Modifier keys (subset of keyboard page)
|
||||
// 0x10000+ → Consumer page (0x0C), usage = value & 0xFFFF
|
||||
|
||||
static constexpr uint16_t HID_USAGE_PAGE_KEYBOARD = 0x07;
|
||||
static constexpr uint16_t HID_USAGE_PAGE_CONSUMER = 0x0C;
|
||||
|
||||
static void emit_keycode(KeyCode key, bool pressed) {
|
||||
uint32_t v = key.value;
|
||||
uint16_t usage_page;
|
||||
uint32_t keycode;
|
||||
|
||||
if (v >= 0x10000) {
|
||||
usage_page = HID_USAGE_PAGE_CONSUMER;
|
||||
keycode = v & 0xFFFF;
|
||||
} else {
|
||||
usage_page = HID_USAGE_PAGE_KEYBOARD;
|
||||
keycode = v;
|
||||
}
|
||||
|
||||
raise_zmk_keycode_state_changed(
|
||||
(struct zmk_keycode_state_changed){
|
||||
.usage_page = usage_page,
|
||||
.keycode = keycode,
|
||||
.implicit_modifiers = 0,
|
||||
.explicit_modifiers = 0,
|
||||
.state = pressed,
|
||||
.timestamp = k_uptime_get(),
|
||||
});
|
||||
}
|
||||
|
||||
bool Key::historyEnabled = false;
|
||||
|
||||
void Key::Press(KeyCode key) {
|
||||
(void)key;
|
||||
}
|
||||
void Key::Press(KeyCode key) { emit_keycode(key, true); }
|
||||
|
||||
void Key::Release(KeyCode key) {
|
||||
(void)key;
|
||||
}
|
||||
void Key::Release(KeyCode key) { emit_keycode(key, false); }
|
||||
|
||||
void Key::Flush() {
|
||||
// ZMK processes events asynchronously via its event queue.
|
||||
// Flush is a no-op — events are dispatched as they're raised.
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue