diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0c4a408 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,56 @@ +# KeySwift Project Overview + +## What is KeySwift +A Linux keyboard remapping tool for GNOME environments that allows application-specific key mappings using JavaScript configuration. + +## Quick Start +```bash +# Build +make + +# Run (after setup) +./keyswift -keyboards "HHKB" -config ~/.config/keyswift/config.js +``` + +## Architecture +- **Language**: Go 1.22 + QuickJS (JavaScript engine) +- **Core Components**: + - `cmd/keyswift/main.go` - Entry point and CLI + - `pkg/bus/` - Event processing and JS engine integration + - `pkg/handler/` - Input device management + - `pkg/evdev/` - Linux input device handling + - `pkg/engine/` - QuickJS JavaScript engine + - `pkg/wininfo/` - Window context detection via D-Bus + +## Key Files +- `examples/config.js` - Configuration examples +- `README.md` - Complete setup guide +- `Makefile` - Build configuration +- `pkg/handler/keystate.go` - New key state management system + +## Key Handling Improvements (July 2025) +- **KeyStateManager**: Added proper physical/virtual keyboard state synchronization +- **Debouncing**: 5ms threshold to prevent rapid key event issues +- **State Sync**: 100ms periodic synchronization to prevent stuck keys +- **Emergency Reset**: Automatic key release on shutdown +- **Event Ordering**: Improved modifier handling with proper press/release sequences +- **Duplicate Prevention**: Eliminates duplicate key events in remapped combinations + +## Key APIs (JavaScript) +```js +KeySwift.getActiveWindowClass() // Get current app +KeySwift.sendKeys(["ctrl", "c"]) // Send key combo +KeySwift.onKeyPress(["cmd", "v"], callback) // Bind keys +``` + +## Setup Requirements +1. Install GNOME extension: `keyswift-gnome-ext` +2. Add user to `input` group +3. Configure udev rules for input access +4. Create `~/.config/keyswift/config.js` + +## Dependencies +- libevdev-dev +- golang 1.22+ +- godbus/dbus/v5 +- QuickJS-go \ No newline at end of file diff --git a/pkg/bus/impl.go b/pkg/bus/impl.go index 496cef4..90a7b32 100644 --- a/pkg/bus/impl.go +++ b/pkg/bus/impl.go @@ -90,23 +90,34 @@ func (m *Impl) SendKeys(keyCodes []keys.Key) { return keys.IsModifier(cloned[i]) }) - // modifier keys first + // Ensure we don't have duplicate key events + seen := make(map[keys.Key]bool) + var uniqueKeys []keys.Key for _, key := range cloned { + if !seen[key] { + seen[key] = true + uniqueKeys = append(uniqueKeys, key) + } + } + + // Press keys in order (modifiers first) + for _, key := range uniqueKeys { err := m.out.WriteEvent(golibevdev.EvKey, key, 1) if err != nil { - slog.Error("failed to send key event", "error", err) + slog.Error("failed to send key press event", "error", err) } } - // send sync event + // Send sync event _ = m.out.WriteEvent(golibevdev.EvSyn, golibevdev.SynReport, 0) - // send release event - for _, key := range cloned { + // Release keys in reverse order (regular keys first, then modifiers) + for i := len(uniqueKeys) - 1; i >= 0; i-- { + key := uniqueKeys[i] _ = m.out.WriteEvent(golibevdev.EvKey, key, 0) } - // send sync event + // Send final sync event _ = m.out.WriteEvent(golibevdev.EvSyn, golibevdev.SynReport, 0) slog.Debug("SendKeys done", "input", keyCodes) diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 9a3f1e1..f6cb7c3 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -7,7 +7,6 @@ import ( "time" "github.com/jialeicui/golibevdev" - "github.com/samber/lo" "github.com/jialeicui/keyswift/pkg/bus" ) @@ -198,6 +197,12 @@ func (m *Handler) processDeviceEvents(dev *InputDevice, modeManager *bus.Impl) { } } + +// Wait waits for all event processing to complete +func (m *Handler) Wait() { + m.wg.Wait() +} + // processEventStack processes a stack of events and determines if they should be handled // return true if the events should be handled, false if the events should be forwarded func (m *Handler) processEventStack( @@ -207,7 +212,10 @@ func (m *Handler) processEventStack( forceNoPassThrough bool, ) bool { // Get currently pressed keys - pressedKeys := lo.Keys(keyStates) + var pressedKeys []golibevdev.KeyEventCode + for key := range keyStates { + pressedKeys = append(pressedKeys, key) + } if len(pressedKeys) == 0 { // No keys are pressed, just forward all events @@ -260,11 +268,6 @@ func (m *Handler) sendSingleKey(code golibevdev.KeyEventCode, value int32) { slog.Debug("send single key", "code", code, "value", value) } -// Wait waits for all event processing to complete -func (m *Handler) Wait() { - m.wg.Wait() -} - // Close closes all input devices func (m *Handler) Close() { for _, dev := range m.devices {