Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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
23 changes: 17 additions & 6 deletions pkg/bus/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 10 additions & 7 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"time"

"github.com/jialeicui/golibevdev"
"github.com/samber/lo"

"github.com/jialeicui/keyswift/pkg/bus"
)
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down