Skip to content

Commit 56ed156

Browse files
committed
chore: add AGENTS.md
1 parent 9fd516d commit 56ed156

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed

AGENTS.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# KeySwift - Agent Guide
2+
3+
## Project Overview
4+
5+
KeySwift is a Linux keyboard remapping tool designed specifically for GNOME desktop environments. It enables application-specific key mappings using JavaScript configuration files, allowing users to customize keyboard shortcuts for different applications.
6+
7+
### Key Features
8+
- **Application-specific remapping**: Define custom keyboard mappings for specific applications based on window class
9+
- **JavaScript configuration**: Flexible configuration using QuickJS JavaScript engine
10+
- **Low-level input handling**: Uses Linux evdev subsystem for direct keyboard input capture
11+
- **D-Bus integration**: Communicates with GNOME Shell extension for window focus tracking
12+
13+
## Technology Stack
14+
15+
- **Language**: Go 1.22+
16+
- **JavaScript Engine**: QuickJS (via buke/quickjs-go)
17+
- **System Dependencies**:
18+
- libevdev-dev (Linux input event handling)
19+
- D-Bus (GNOME integration)
20+
- **Key Go Dependencies**:
21+
- `github.com/godbus/dbus/v5` - D-Bus bindings for Go
22+
- `github.com/buke/quickjs-go` - QuickJS JavaScript engine bindings
23+
- `github.com/jialeicui/golibevdev` - Linux evdev wrapper
24+
- `github.com/samber/lo` - Go utilities library
25+
- `github.com/stretchr/testify` - Testing framework
26+
27+
## Project Structure
28+
29+
```
30+
.
31+
├── cmd/keyswift/main.go # Application entry point and CLI
32+
├── pkg/
33+
│ ├── bus/ # Event processing and coordination
34+
│ │ ├── impl.go # Main bus implementation
35+
│ │ ├── mode.go # Event type definitions
36+
│ │ └── session.go # Per-event processing session
37+
│ ├── engine/ # JavaScript engine integration
38+
│ │ ├── interfaces.go # Engine and Bus interfaces
39+
│ │ └── quickjs.go # QuickJS implementation
40+
│ ├── evdev/ # Input device management
41+
│ │ ├── evdev.go # Core types
42+
│ │ └── overview.go # Device enumeration
43+
│ ├── handler/ # Input event handling
44+
│ │ ├── handler.go # Main event processor
45+
│ │ └── modifier.go # Modifier key state tracking
46+
│ ├── keys/ # Key code mappings
47+
│ │ └── keys.go # Key name to code conversion
48+
│ ├── utils/ # Utilities
49+
│ │ ├── config.go # Configuration path helpers
50+
│ │ └── cache/ # Caching utilities
51+
│ └── wininfo/ # Window information
52+
│ ├── wininfo.go # Interface definitions
53+
│ └── dbus/ # D-Bus implementation
54+
├── examples/
55+
│ └── config.js # Example configuration
56+
├── Makefile # Build automation
57+
└── go.mod # Go module definition
58+
```
59+
60+
## Architecture
61+
62+
### Data Flow
63+
64+
1. **Input Capture** (`pkg/handler/`)
65+
- Grabs physical keyboard devices via evdev
66+
- Processes raw key events
67+
- Tracks modifier key states
68+
- Manages key press/release sequences
69+
70+
2. **Event Processing** (`pkg/bus/`)
71+
- Receives key events from handler
72+
- Creates isolated session per event
73+
- Executes JavaScript configuration
74+
- Routes output to virtual keyboard
75+
76+
3. **JavaScript Engine** (`pkg/engine/`)
77+
- Compiles user configuration to bytecode
78+
- Exposes `KeySwift` global object with APIs:
79+
- `getActiveWindowClass()` - Get current application
80+
- `sendKeys(keys[])` - Send key combination
81+
- `onKeyPress(keys[], callback)` - Register key handler
82+
- Fast-path filtering for unregistered key combinations
83+
84+
4. **Window Detection** (`pkg/wininfo/`)
85+
- D-Bus service receives window info from GNOME extension
86+
- Falls back to degraded mode if D-Bus unavailable
87+
- Provides active window class for conditional mappings
88+
89+
5. **Output** (via golibevdev)
90+
- Creates virtual keyboard device
91+
- Sends remapped key events
92+
- Proper modifier handling (press/release ordering)
93+
94+
## Build Commands
95+
96+
```bash
97+
# Build the binary
98+
make
99+
100+
# Build with version info
101+
go build -ldflags "-X main.version=$(VERSION) -X main.commit=$(COMMIT)" -o keyswift cmd/keyswift/main.go
102+
103+
# Run tests (requires sudo for evdev access)
104+
sudo go test -v ./...
105+
106+
# Run specific package tests
107+
sudo go test -v ./pkg/utils/cache/
108+
```
109+
110+
## Testing
111+
112+
### Test Structure
113+
- Unit tests use `github.com/stretchr/testify`
114+
- Tests requiring evdev access need `sudo`
115+
- Key test files:
116+
- `pkg/utils/cache/cache_test.go` - Cache implementation tests
117+
- `pkg/evdev/overview_test.go` - Device enumeration tests
118+
119+
### CI/CD
120+
- GitHub Actions workflow in `.github/workflows/unit-test.yml`
121+
- Runs on push to main and pull requests
122+
- Requires `libevdev-dev` package
123+
- Tests run with Go 1.23
124+
125+
## Configuration
126+
127+
### JavaScript API
128+
129+
The configuration file (`~/.config/keyswift/config.js`) has access to:
130+
131+
```javascript
132+
const KeySwift = {
133+
// Returns the window class of the currently focused application
134+
getActiveWindowClass: () => string,
135+
136+
// Sends a key combination (modifiers: ctrl, alt, cmd/meta/super, shift)
137+
sendKeys: (keys: string[]) => void,
138+
139+
// Registers a callback for specific key combination
140+
// Must be called at top level, not inside callbacks or conditionals
141+
onKeyPress: (keys: string[], callback: () => void) => void,
142+
}
143+
```
144+
145+
### Example Configuration
146+
147+
See `examples/config.js` for a comprehensive example including:
148+
- Terminal-specific mappings (kitty, GNOME Terminal, Ghostty)
149+
- IDE mappings (JetBrains, Cursor, Sublime Text)
150+
- macOS-like shortcuts for Linux
151+
- Chrome tab switching shortcuts
152+
- Emacs-style navigation
153+
154+
## Runtime Requirements
155+
156+
### System Setup
157+
1. **GNOME Extension**: Install `keyswift-gnome-ext` from https://github.com/jialeicui/keyswift-gnome-ext
158+
2. **User Permissions**: Add user to `input` group
159+
3. **udev Rules**: Create `/etc/udev/rules.d/input.rules`:
160+
```
161+
KERNEL=="uinput", GROUP="input", TAG+="uaccess"
162+
```
163+
4. **Restart** system to apply group and udev changes
164+
165+
### Running
166+
167+
```bash
168+
# List available keyboards and filter by pattern
169+
./keyswift -keyboards "HHKB" -config ~/.config/keyswift/config.js
170+
171+
# Multiple keyboards (comma-separated)
172+
./keyswift -keyboards "HHKB,Logitech" -config ~/.config/keyswift/config.js
173+
174+
# Verbose logging
175+
./keyswift -keyboards "HHKB" -config ~/.config/keyswift/config.js -verbose
176+
177+
# Custom output device name
178+
./keyswift -keyboards "HHKB" -output-device-name "my-keyboard" -config ~/.config/keyswift/config.js
179+
```
180+
181+
**Important**: Do not run with `sudo`. The application requires user-level permissions with `input` group membership.
182+
183+
## Code Style Guidelines
184+
185+
### Go Conventions
186+
- Standard Go formatting (`gofmt`)
187+
- Package comments for all public packages
188+
- Interface definitions in dedicated files (e.g., `interfaces.go`)
189+
- Error wrapping with context using `fmt.Errorf("...: %w", err)`
190+
- Structured logging using `log/slog`
191+
192+
### Naming
193+
- Interfaces with `-er` suffix (e.g., `WinGetter`, `Engine`)
194+
- Implementation types with descriptive names (e.g., `Impl`, `QuickJS`, `Receiver`)
195+
- Constants for magic strings (e.g., `FuncSendKeys`, `KeySwiftObj`)
196+
197+
### Error Handling
198+
- Return errors with context
199+
- Use `slog.Error()` for operational errors
200+
- Graceful degradation (e.g., `DegradedReceiver` for D-Bus failures)
201+
202+
## Security Considerations
203+
204+
1. **Input Device Access**: Requires membership in `input` group
205+
2. **D-Bus Communication**: Exposes service on session bus
206+
3. **JavaScript Execution**: User-provided scripts run in QuickJS sandbox with memory limits:
207+
- Memory limit: 1280 KB
208+
- GC threshold: 2560 KB
209+
- Max stack size: 65534
210+
- Execution timeout: 0 (disabled)
211+
212+
## Key Implementation Details
213+
214+
### Modifier Key Handling
215+
- Modifier keys (Ctrl, Alt, Meta) are tracked separately
216+
- Pass-through mode for modifiers enables browser shortcuts (e.g., Ctrl+Click)
217+
- Modifier release events are synthesized when remapping begins
218+
219+
### Key State Management
220+
- Debouncing with 5ms threshold
221+
- Periodic state synchronization (100ms)
222+
- Emergency key release on shutdown
223+
- Duplicate event prevention
224+
225+
### Fast Path Optimization
226+
- JavaScript engine tracks registered key combinations
227+
- Unregistered combinations bypass script execution
228+
- Key code caching to avoid repeated lookups
229+
230+
## Related Projects
231+
232+
- **GNOME Extension**: https://github.com/jialeicui/keyswift-gnome-ext
233+
- Inspired by: xremap, kmonad, autokey, AutoHotkey

0 commit comments

Comments
 (0)