input

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 16 Imported by: 0

README

input-go

Pure-Go mouse and keyboard synthesis for macOS.

Single binary, zero cgo, go install-able. Built on CGEvent (Quartz Event Services) via purego + an embedded companion dylib — the same pattern as sckit-go and kinrec.

input-go is part of KinKit — a family of pure-Go macOS system libraries powering the LocalKin agent swarm.

go install github.com/LocalKinAI/input-go/cmd/input@latest
input click 400 300
input type "hello, world"
input hotkey cmd c

Features

  • Mouse: move, click (left/right/middle, N-clicks), drag with eased animation, smooth motion, scroll with momentum.
  • Keyboard: press/release individual keys, type arbitrary UTF-8 (including CJK + emoji — bypasses keyboard layout), hotkey combos with any modifier stack.
  • IME-safe paste (v0.3): PasteText routes CJK / multibyte text through the macOS pasteboard + ⌘V instead of synthesizing one key event per character — Pinyin / Wubi / Kotoeri front-ends keep up. Original clipboard is restored after the paste settles.
  • Per-process routing (v0.2): WithPID(pid) posts events directly to a target app via CGEventPostToPid, so the user's foreground window keeps focus while the agent operates a different app.
  • Defer-friendly modifier holding (v0.2): Hold(ctx, mods, keys) returns a release closure designed for defer — eliminates the "stuck modifier" class of bug.
  • Geometry: current cursor position, main screen size.
  • Permission: probe / prompt for Accessibility trust.
  • No cgo: downstream projects stay pure Go. The ObjC companion dylib is //go:embedded and extracted to ~/Library/Caches on first call.
  • Universal: single Mach-O ships both arm64 and x86_64 (~60 KB).

Install

# CLI
go install github.com/LocalKinAI/input-go/cmd/input@latest

# Library
go get github.com/LocalKinAI/input-go

Requires macOS 12+ and Go 1.22+.

Permission

macOS requires the invoking binary to be listed in System Settings → Privacy & Security → Accessibility for synthesized events to take effect. Without permission, calls return nil (no error) but events have no visible effect — this matches the silent failure model of CGEvent itself.

if !input.Trusted() {
    if !input.PromptTrust() {
        log.Fatal("accessibility permission denied — grant in System Settings")
    }
}

Library usage

package main

import (
    "context"
    "log"
    "time"

    "github.com/LocalKinAI/input-go"
)

func main() {
    ctx := context.Background()
    if err := input.Load(); err != nil {
        log.Fatal(err)
    }
    if err := input.RequireTrust(); err != nil {
        log.Fatal(err)
    }

    // Smoothly move cursor to (800, 400) over 300 ms
    input.MoveSmooth(ctx, 800, 400, 300*time.Millisecond)

    // Double-click there
    input.DoubleClick(ctx, 800, 400)

    // Type a string into the focused field (Latin-1 — fast, key-by-key)
    input.Type(ctx, "Hello world")

    // CJK / multibyte / IME-active app — paste through clipboard so
    // Pinyin / Wubi / Kotoeri don't drop characters at full keyboard
    // rate. v0.3 addition; original clipboard is restored after.
    input.PasteText(ctx, "你好世界 👋")

    // Cmd+Shift+T (reopen closed tab)
    input.Hotkey(ctx, input.ModCommand|input.ModShift, input.KeyT)

    // Drag from (100,100) to (500,100) over 200 ms
    input.Drag(ctx, 100, 100, 500, 100, 200*time.Millisecond)
}

CLI usage

# Mouse
input move 400 300                          # jump cursor
input move 400 300 --smooth 300ms           # animate
input click                                 # click at current pos
input click 400 300 --button right          # right-click at pos
input click --count 2                       # double-click at current pos
input drag 100 100 500 100 --duration 250ms

# Scroll
input scroll 0 -200                         # scroll up 200 px
input scroll 0 -200 --smooth 500ms

# Keyboard
input type "hello world"
input type "混合内容 Hello 👋" --delay 30ms
input press enter
input press f5
input hotkey cmd c                          # copy
input hotkey cmd+shift t                    # reopen closed tab
input hotkey cmd+shift+3                    # screenshot

# Introspection
input cursor                                # prints "X Y"
input screen                                # prints "W H"
input trust                                 # prints 1 or 0
input trust --prompt                        # triggers system dialog
input version

How it works

input-go follows the embedded dylib pattern documented in Paper #9 of localkin.dev/papers.

Go code  ─── purego.Dlopen ────► libinput_sync.dylib (embedded)
                                     │
                                     └──► CGEvent* APIs
  • objc/input_events.m — 200 LOC ObjC shim exposing 8 C-ABI functions (input_mouse_event, input_key, input_type_unicode, etc.).
  • internal/dylib/libinput_sync.dylib — universal Mach-O, built by make dylib, committed to the repo so go get works without requiring downstream users to install clang.
  • On first call, Load() extracts the embedded bytes to ~/Library/Caches/input-go/<content-hash>/libinput_sync.dylib and Dlopens from there.

Known limitations (current as of v0.3)

  • macOS only. Linux/X11 and Windows/WinAPI would need sibling backends; out of scope.
  • No keyboard event capture — input-go synthesizes events, it doesn't listen for them. KinClaw uses kinax-go's Observer (push-based AX events) for the agent-observes-human use case instead, since AX gives semantic notifications (AXFocusedUIElement Changed, AXValueChanged) without polling. A raw-event input-go/listen is therefore deferred indefinitely — file an issue if you need it.
  • Unicode typing bypasses keyboard layout. input.Type("A") emits a literal A character event, not Shift+a. Apps that watch raw keycodes (games, key-remappers) won't see a modifier press. Use input.Hotkey(input.ModShift, input.KeyA) if you need layout-aware behavior. For CJK / IME contexts, prefer input.PasteText (v0.3).
  • PasteText clobbers non-text clipboard. The pasteboard restore step only handles plain text — image / file / RTF clipboards do not survive a PasteText round-trip. Documented at the call site.
  • CapsLock state is not tracked. If the user has CapsLock engaged, typed letters will come out uppercase regardless of your intent.
  • Multi-display coordinate systems are passed through unchanged — (0, 0) is the top-left of the main display; external displays live in their configured coordinate space.
  • Tested only on macOS 26.3 arm64 so far; Intel + macOS 14/15 verification pending CI.

Roadmap

  • v0.2WithPID background-safe input routing (per-process CGEventPostToPid) + Hold defer-friendly modifier scope. Shipped 2026-04-28.
  • v0.3PasteText IME-safe text injection via pasteboard + ⌘V + restore + ReadClipboard / WriteClipboard building blocks. Shipped 2026-05-07.
  • v0.4 (planned) — WithDelay(d) PostOption for screen demos / rate-limited apps; WithEventTap to pick session vs HID tap; MouseDown / MouseUp raw primitives for callers building their own gesture engines.
  • v0.5 (planned) — Raw IOKit HID path for games and apps that bypass CGEventTap (e.g. anti-cheat-protected games — won't work there even with HID, but we'll document the failure clearly).
  • Cross-platform backends — only if a user files an issue asking.

Contributing

git clone https://github.com/LocalKinAI/input-go
cd input-go
make dylib     # rebuild universal Mach-O after ObjC changes
make test      # unit tests (safe — no events synthesized)
make test-integration   # needs Accessibility permission, actually moves cursor
make lint      # go vet + staticcheck + golangci-lint

License

MIT. See LICENSE.

See also

Documentation

Overview

Package input is a pure-Go macOS mouse and keyboard synthesis library.

input uses CGEvent (Quartz Event Services) to post mouse and keyboard events system-wide. It's the foundation for automation, UI testing, screen-demo tooling, and remote-control agents — anything that needs to drive the cursor or type text.

Quick start

ctx := context.Background()
if err := input.Load(); err != nil { log.Fatal(err) }

// Move cursor and click
input.ClickAt(ctx, 400, 300)

// Type text
input.Type(ctx, "hello, world")

// Cmd+C hotkey
input.Hotkey(ctx, input.ModCommand, input.KeyC)

Permissions

macOS requires the invoking binary to be listed in System Settings → Privacy & Security → Accessibility for synthesized events to take effect. Without permission, calls return nil (no error) but the events have no visible effect — this matches macOS's silent-failure model. Use Trusted to check, and PromptTrust to trigger the system dialog.

Dylib placement

input-go ships a universal (arm64+x86_64) companion dylib via //go:embed. On the first call into the package, the embedded bytes are extracted to ~/Library/Caches/input-go/<hash>/libinput_sync.dylib and Dlopened. Set DylibPath to a non-empty value before the first call if you ship a custom-built or patched dylib.

Index

Constants

View Source
const Version = "0.3.0"

Version is the semantic-version tag of this package. Kept in sync with git tags; updated per release.

Variables

View Source
var DylibPath = ""

DylibPath is an optional override for the location of libinput_sync.dylib. Default (empty): extract the embedded copy to the user cache directory. Set to a non-empty path BEFORE the first call into this package if shipping a custom-built dylib.

View Source
var ErrInvalidArg = errors.New("input: invalid argument")

ErrInvalidArg is returned for malformed inputs (e.g. an empty key, a negative click count).

View Source
var ErrNotTrusted = errors.New("input: accessibility permission not granted")

ErrNotTrusted is returned when the current process lacks Accessibility permission AND the caller explicitly requested a trust check (see RequireTrust). Regular input calls DO NOT return this error — they silently no-op, matching macOS behavior.

Functions

func Click

func Click(ctx context.Context, button MouseButton, clicks int, opts ...PostOption) error

Click presses and releases `button` at the current cursor position. `clicks` is the click count (1 = single, 2 = double, 3 = triple). A zero or negative `clicks` is treated as 1.

func ClickAt

func ClickAt(ctx context.Context, x, y float64, opts ...PostOption) error

ClickAt moves the cursor to (x, y) and single-left-clicks. This is the most common convenience — 90% of automation needs it.

func ClickAtButton

func ClickAtButton(ctx context.Context, x, y float64, button MouseButton, clicks int, opts ...PostOption) error

ClickAtButton is like ClickAt but with a configurable button and click count.

func CursorPosition

func CursorPosition() (x, y float64, err error)

CursorPosition returns the current cursor location in global screen coordinates (origin at top-left).

func DoubleClick

func DoubleClick(ctx context.Context, x, y float64, opts ...PostOption) error

DoubleClick is shorthand for ClickAtButton(..., ButtonLeft, 2).

func Drag

func Drag(ctx context.Context, fromX, fromY, toX, toY float64, duration time.Duration, opts ...PostOption) error

Drag performs a "mouse-down → move → mouse-up" sequence: press the left button at `from`, interpolate the cursor toward `to` over `duration`, release. Used for drag-select, drag-to-reorder, slider control, etc.

func Hold added in v0.2.0

func Hold(ctx context.Context, mods Modifier, keys []Key, opts ...PostOption) func()

Hold presses the given modifiers and keys, then returns a release closure that lifts them. Designed for `defer` to guarantee teardown even on panic or early return:

defer input.Hold(ctx, input.ModShift, nil)()
input.Press(ctx, input.KeyTab)   // Shift+Tab
input.Press(ctx, input.KeyTab)   // still Shift+Tab

`keys` is a slice (rather than variadic) because the function already has a variadic `opts` tail; pass nil when only modifiers are needed:

defer input.Hold(ctx, input.ModCommand, nil, input.WithPID(p))()

Without this helper, any error path between KeyDown and KeyUp leaks a "stuck" modifier — every subsequent input gets corrupted at the OS level (sticky shift / cmd / etc.) until the user notices and taps the modifier manually. Hold + defer eliminates the whole class of bug.

Multiple keys are pressed in order, released in reverse order — so `Hold(ctx, ModCommand|ModShift)` releases Shift before Command, the natural teardown order.

The release closure is idempotent — calling it twice is safe but only the first call has effect. Release errors are silently dropped (macOS's silent-failure model means there's no useful action anyway).

Returns a no-op release if Load failed or ctx is already canceled.

func Hotkey

func Hotkey(ctx context.Context, mods Modifier, key Key, opts ...PostOption) error

Hotkey presses `key` with `mods` held: down-modifiers → down-key → up-key → up-modifiers. This is how you send Cmd+C, Cmd+Shift+T, etc.

input.Hotkey(ctx, input.ModCommand, input.KeyC)                 // copy
input.Hotkey(ctx, input.ModCommand|input.ModShift, input.KeyT)  // reopen tab

func KeyDown

func KeyDown(ctx context.Context, key Key, mods Modifier, opts ...PostOption) error

KeyDown posts a key-down event for `key`. Optionally carries modifier flags — pass 0 for no modifiers.

func KeyUp

func KeyUp(ctx context.Context, key Key, mods Modifier, opts ...PostOption) error

KeyUp posts a key-up event for `key`.

func Load

func Load() error

Load explicitly loads the companion dylib. It's idempotent: subsequent calls return the same cached error (or nil).

Resolution order:

  1. If DylibPath is non-empty, use it (user override).
  2. Otherwise, extract the embedded universal dylib to the cache directory and Dlopen from there.

Load is called automatically by every public function; the exported form exists so applications can fail fast at startup.

func Move

func Move(ctx context.Context, x, y float64, opts ...PostOption) error

Move jumps the cursor instantly to (x, y) in global screen coordinates. (0, 0) is the top-left of the main display. Pass WithPID to route the event to a specific app without focus steal.

func MoveBy

func MoveBy(ctx context.Context, dx, dy float64, opts ...PostOption) error

MoveBy offsets the cursor by (dx, dy) from its current position.

func MoveSmooth

func MoveSmooth(ctx context.Context, x, y float64, duration time.Duration, opts ...PostOption) error

MoveSmooth moves the cursor from its current position to (x, y) by linearly interpolating through intermediate points. `duration` controls total animation length; shorter values feel snappier. A `duration` of 0 degenerates to Move.

This is the animation primitive used for "screen demo" recordings where a teleporting cursor looks jarring.

func PasteText added in v0.3.0

func PasteText(ctx context.Context, text string, opts ...PostOption) error

PasteText injects a string by writing to the macOS clipboard, firing ⌘V via the standard Hotkey path, and restoring the user's previous clipboard text ~150ms later.

Why a separate function rather than just Type: Type synthesizes a key event per character at full keyboard rate (~100 char/s). IME front-ends (Pinyin / Wubi / Kotoeri) frequently can't keep up with that rate — characters get dropped or the IME's composition state desyncs. PasteText sidesteps the IME entirely by routing through the system pasteboard.

Restore semantics: PasteText always saves the previous clipboard before writing + restores it after the keystroke. Non-text pasteboard content (image / file / RTF) does NOT survive the round-trip — by macOS pasteboard design — but plain text (the 95% case) does. If you don't want the restore, set the clipboard yourself with WriteClipboard after PasteText returns.

Routing: [PostOption]s (e.g. WithPID) apply to the ⌘V keystroke, so paste can target a background app without stealing focus from the user's foreground window. The pbcopy/pbpaste shell-outs are system-wide either way — there's only one macOS pasteboard.

if err := input.PasteText(ctx, "你好世界"); err != nil { ... }
input.PasteText(ctx, longChineseText, input.WithPID(pid))

Errors:

  • pbcopy / pbpaste failures propagate as "pbcopy: …" / "pbpaste: …". Rare unless running in a sandbox without /usr/bin.
  • The ⌘V keystroke uses the same error chain as Hotkey.

func Press

func Press(ctx context.Context, key Key, opts ...PostOption) error

Press taps a key: down then up. This is the common case for any key that doesn't need to be held.

func PromptTrust

func PromptTrust() bool

PromptTrust triggers the system "<app> wants access to control your computer" dialog if permission has not yet been granted. Returns current trust state (true if granted, false if the user hasn't responded yet or denied).

This is a one-shot prompt — subsequent calls return the current state without showing the dialog again (until the user resets TCC).

func ReadClipboard added in v0.3.0

func ReadClipboard(ctx context.Context) (string, error)

ReadClipboard returns the macOS clipboard's current text content via pbpaste. Useful as a building block for code that needs to inspect / round-trip the pasteboard without going through the whole PasteText flow.

func RequireTrust

func RequireTrust() error

RequireTrust returns nil if Accessibility permission is granted, or ErrNotTrusted otherwise. Convenience for callers that want a hard fail rather than silent no-ops.

func ResolvedDylibPath

func ResolvedDylibPath() string

ResolvedDylibPath returns the filesystem path that Load used (or would use) to Dlopen the dylib. Intended for diagnostics.

func RightClick

func RightClick(ctx context.Context, x, y float64, opts ...PostOption) error

RightClick is shorthand for ClickAtButton(..., ButtonRight, 1).

func ScreenSize

func ScreenSize() (w, h float64, err error)

ScreenSize returns the main display's pixel dimensions.

func Scroll

func Scroll(ctx context.Context, dx, dy int, opts ...PostOption) error

Scroll posts a scroll-wheel event. `dy` is vertical (positive scrolls content up — same as flicking your finger upward on a trackpad); `dx` is horizontal (positive scrolls right). Units are pixels.

func ScrollSmooth

func ScrollSmooth(ctx context.Context, dx, dy int, duration time.Duration, opts ...PostOption) error

ScrollSmooth posts a series of smaller scroll events to approximate a single larger scroll. Looks more natural than a single big jump and avoids "overshoot" on momentum-scrolling apps.

func Trusted

func Trusted() bool

Trusted returns true if the current process has Accessibility permission granted. Does not prompt — use PromptTrust for that.

func Type

func Type(ctx context.Context, text string, opts ...PostOption) error

Type synthesizes keyboard events that produce `text` in the currently focused text field. Works for arbitrary UTF-8 including emoji and CJK, because it uses CGEventKeyboardSetUnicodeString rather than translating through the current keyboard layout.

Note: because this bypasses the keyboard layout, typing "A" is a literal A glyph, not "shift+a". Apps that listen for raw keycodes (games, key remappers) won't see a modifier press.

func TypeSlow

func TypeSlow(ctx context.Context, text string, perCharDelay time.Duration, opts ...PostOption) error

TypeSlow is like Type but inserts `perCharDelay` between characters. Useful for demos where "instant paste" looks artificial, or for apps that throttle rapid-fire input.

func WriteClipboard added in v0.3.0

func WriteClipboard(ctx context.Context, text string) error

WriteClipboard writes text to the macOS clipboard via pbcopy. Companion to ReadClipboard. Use this if you want to populate the clipboard without immediately firing ⌘V.

Types

type Key

type Key int32

Key is a macOS virtual keycode. Values match <Carbon/HIToolbox/Events.h>.

Use the named constants (KeyA, KeySpace, ...) rather than numeric values — Apple's keycodes are ANSI-layout specific and not obvious.

const (
	KeyA Key = 0x00
	KeyB Key = 0x0B
	KeyC Key = 0x08
	KeyD Key = 0x02
	KeyE Key = 0x0E
	KeyF Key = 0x03
	KeyG Key = 0x05
	KeyH Key = 0x04
	KeyI Key = 0x22
	KeyJ Key = 0x26
	KeyK Key = 0x28
	KeyL Key = 0x25
	KeyM Key = 0x2E
	KeyN Key = 0x2D
	KeyO Key = 0x1F
	KeyP Key = 0x23
	KeyQ Key = 0x0C
	KeyR Key = 0x0F
	KeyS Key = 0x01
	KeyT Key = 0x11
	KeyU Key = 0x20
	KeyV Key = 0x09
	KeyW Key = 0x0D
	KeyX Key = 0x07
	KeyY Key = 0x10
	KeyZ Key = 0x06

	Key0 Key = 0x1D
	Key1 Key = 0x12
	Key2 Key = 0x13
	Key3 Key = 0x14
	Key4 Key = 0x15
	Key5 Key = 0x17
	Key6 Key = 0x16
	Key7 Key = 0x1A
	Key8 Key = 0x1C
	Key9 Key = 0x19

	KeyReturn        Key = 0x24
	KeyTab           Key = 0x30
	KeySpace         Key = 0x31
	KeyDelete        Key = 0x33 // Backspace on PC keyboards
	KeyEscape        Key = 0x35
	KeyForwardDelete Key = 0x75 // fn+delete / dedicated Del key

	KeyLeftCommand  Key = 0x37
	KeyLeftShift    Key = 0x38
	KeyCapsLock     Key = 0x39
	KeyLeftOption   Key = 0x3A
	KeyLeftControl  Key = 0x3B
	KeyRightShift   Key = 0x3C
	KeyRightOption  Key = 0x3D
	KeyRightControl Key = 0x3E
	KeyFn           Key = 0x3F

	KeyArrowLeft  Key = 0x7B
	KeyArrowRight Key = 0x7C
	KeyArrowDown  Key = 0x7D
	KeyArrowUp    Key = 0x7E

	KeyF1  Key = 0x7A
	KeyF2  Key = 0x78
	KeyF3  Key = 0x63
	KeyF4  Key = 0x76
	KeyF5  Key = 0x60
	KeyF6  Key = 0x61
	KeyF7  Key = 0x62
	KeyF8  Key = 0x64
	KeyF9  Key = 0x65
	KeyF10 Key = 0x6D
	KeyF11 Key = 0x67
	KeyF12 Key = 0x6F

	KeyHome     Key = 0x73
	KeyEnd      Key = 0x77
	KeyPageUp   Key = 0x74
	KeyPageDown Key = 0x79

	KeyMinus        Key = 0x1B
	KeyEqual        Key = 0x18
	KeyLeftBracket  Key = 0x21
	KeyRightBracket Key = 0x1E
	KeyBackslash    Key = 0x2A
	KeySemicolon    Key = 0x29
	KeyQuote        Key = 0x27
	KeyComma        Key = 0x2B
	KeyPeriod       Key = 0x2F
	KeySlash        Key = 0x2C
	KeyGrave        Key = 0x32
)

US-ANSI virtual keycodes — stable across macOS versions. Source: <HIToolbox/Events.h> kVK_* constants.

func KeyByName

func KeyByName(name string) (Key, bool)

KeyByName looks up a Key by its common name (case-insensitive). Used by the CLI so `input key --press enter` works. Returns (0, false) if the name is unknown.

type Modifier

type Modifier uint64

Modifier is a bitmask of keyboard modifier flags. Combine with bitwise OR: `input.ModCommand | input.ModShift`. Values match CGEventFlags.

const (
	// ModCommand is the Command (⌘) modifier.
	ModCommand Modifier = 1 << 20 // kCGEventFlagMaskCommand
	// ModShift is the Shift (⇧) modifier.
	ModShift Modifier = 1 << 17 // kCGEventFlagMaskShift
	// ModOption is the Option/Alt (⌥) modifier.
	ModOption Modifier = 1 << 19 // kCGEventFlagMaskAlternate
	// ModControl is the Control (⌃) modifier.
	ModControl Modifier = 1 << 18 // kCGEventFlagMaskControl
	// ModFunction is the Function (fn) modifier.
	ModFunction Modifier = 1 << 23 // kCGEventFlagMaskSecondaryFn
)

func ParseModifiers

func ParseModifiers(s string) (Modifier, bool)

ParseModifiers parses a comma- or '+'-delimited modifier string like "cmd+shift" or "cmd,option" into a Modifier bitmask. Unknown tokens return (0, false). Empty string returns (0, true).

type MouseButton

type MouseButton int32

MouseButton enumerates the three mouse buttons that CGEvent understands.

const (
	ButtonLeft  MouseButton = 0
	ButtonRight MouseButton = 1
	ButtonOther MouseButton = 2
)

type PostOption added in v0.2.0

type PostOption func(*postConfig)

PostOption tunes how an event is posted to the OS. Pass via the variadic `opts` tail of any event-posting function.

func WithPID added in v0.2.0

func WithPID(pid int32) PostOption

WithPID routes events directly to the given process via CGEventPostToPid, avoiding focus steal — your foreground app stays foreground while input-go drives a background app. pid=0 (the default when the option is omitted) keeps the legacy system-wide HID event tap behavior, which moves focus to the targeted control's owning app.

Verified working on Lark / VSCode / Chrome and other Electron + Web View hosts. Some Apple sandboxed apps (newer Mail / Messages) may ignore PID-targeted events — fall back to the default if you see no effect.

input.Click(ctx, 400, 300, input.WithPID(int32(targetPID)))
input.Type(ctx, "hello", input.WithPID(int32(targetPID)))
input.Hotkey(ctx, input.ModCommand, input.KeyS, input.WithPID(int32(targetPID)))

Directories

Path Synopsis
cmd
input command
Command input drives the mouse and keyboard from the command line.
Command input drives the mouse and keyboard from the command line.
internal
dylib
Package dylib embeds the ObjC companion library so downstream users can simply `go get` input-go without building C code on their machine.
Package dylib embeds the ObjC companion library so downstream users can simply `go get` input-go without building C code on their machine.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL