462 lines
23 KiB
Go
462 lines
23 KiB
Go
package input
|
|
|
|
import "time"
|
|
|
|
// Command is a semantic request; native callbacks never execute application work.
|
|
type Command string
|
|
|
|
const (
|
|
SelectPreviousSpeechSetting Command = "select-previous-speech-setting"
|
|
SelectNextSpeechSetting Command = "select-next-speech-setting"
|
|
DecreaseSpeechSetting Command = "decrease-speech-setting"
|
|
IncreaseSpeechSetting Command = "increase-speech-setting"
|
|
SaveSpeechSettings Command = "save-speech-settings"
|
|
PresentTime Command = "present-time"
|
|
PresentDate Command = "present-date"
|
|
PresentClipboard Command = "present-clipboard"
|
|
ShowSpeechHistory Command = "show-speech-history"
|
|
ToggleSpeechMonitor Command = "toggle-speech-monitor"
|
|
CycleSpeechMode Command = "cycle-speech-mode"
|
|
CycleProgressBarOutput Command = "cycle-progress-bar-output"
|
|
PresentVersion Command = "present-version"
|
|
ToggleLearnMode Command = "toggle-learn-mode"
|
|
ShowActions Command = "show-actions"
|
|
InternalUIKey Command = "internal-ui-key"
|
|
InternalUIDismiss Command = "internal-ui-dismiss"
|
|
LearnKey Command = "learn-key"
|
|
LearnDocumentation Command = "learn-documentation"
|
|
LearnDefaultShortcuts Command = "learn-default-shortcuts"
|
|
LearnApplicationShortcuts Command = "learn-application-shortcuts"
|
|
StickyFocusMode Command = "sticky-focus-mode"
|
|
StickyBrowseMode Command = "sticky-browse-mode"
|
|
ToggleDocumentMode Command = "toggle-document-mode"
|
|
DocumentNextHeading Command = "document-next-heading"
|
|
DocumentPreviousHeading Command = "document-previous-heading"
|
|
DocumentNextLink Command = "document-next-link"
|
|
DocumentPreviousLink Command = "document-previous-link"
|
|
DocumentNextButton Command = "document-next-button"
|
|
DocumentPreviousButton Command = "document-previous-button"
|
|
DocumentNextCheckBox Command = "document-next-check-box"
|
|
DocumentPreviousCheckBox Command = "document-previous-check-box"
|
|
DocumentNextList Command = "document-next-list"
|
|
DocumentPreviousList Command = "document-previous-list"
|
|
DocumentNextSeparator Command = "document-next-separator"
|
|
DocumentPreviousSeparator Command = "document-previous-separator"
|
|
DocumentNextHeadingAtLevel1 Command = "document-next-heading-at-level-1"
|
|
DocumentPreviousHeadingAtLevel1 Command = "document-previous-heading-at-level-1"
|
|
DocumentNextHeadingAtLevel2 Command = "document-next-heading-at-level-2"
|
|
DocumentPreviousHeadingAtLevel2 Command = "document-previous-heading-at-level-2"
|
|
DocumentNextHeadingAtLevel3 Command = "document-next-heading-at-level-3"
|
|
DocumentPreviousHeadingAtLevel3 Command = "document-previous-heading-at-level-3"
|
|
DocumentNextHeadingAtLevel4 Command = "document-next-heading-at-level-4"
|
|
DocumentPreviousHeadingAtLevel4 Command = "document-previous-heading-at-level-4"
|
|
DocumentNextHeadingAtLevel5 Command = "document-next-heading-at-level-5"
|
|
DocumentPreviousHeadingAtLevel5 Command = "document-previous-heading-at-level-5"
|
|
DocumentNextHeadingAtLevel6 Command = "document-next-heading-at-level-6"
|
|
DocumentPreviousHeadingAtLevel6 Command = "document-previous-heading-at-level-6"
|
|
DocumentNextBlockQuote Command = "document-next-block-quote"
|
|
DocumentPreviousBlockQuote Command = "document-previous-block-quote"
|
|
DocumentNextComboBox Command = "document-next-combo-box"
|
|
DocumentPreviousComboBox Command = "document-previous-combo-box"
|
|
DocumentNextEntry Command = "document-next-entry"
|
|
DocumentPreviousEntry Command = "document-previous-entry"
|
|
DocumentNextFormField Command = "document-next-form-field"
|
|
DocumentPreviousFormField Command = "document-previous-form-field"
|
|
DocumentNextImage Command = "document-next-image"
|
|
DocumentPreviousImage Command = "document-previous-image"
|
|
DocumentNextLandmark Command = "document-next-landmark"
|
|
DocumentPreviousLandmark Command = "document-previous-landmark"
|
|
DocumentNextListItem Command = "document-next-list-item"
|
|
DocumentPreviousListItem Command = "document-previous-list-item"
|
|
DocumentNextLiveRegion Command = "document-next-live-region"
|
|
DocumentPreviousLiveRegion Command = "document-previous-live-region"
|
|
DocumentNextParagraph Command = "document-next-paragraph"
|
|
DocumentPreviousParagraph Command = "document-previous-paragraph"
|
|
DocumentNextRadioButton Command = "document-next-radio-button"
|
|
DocumentPreviousRadioButton Command = "document-previous-radio-button"
|
|
DocumentNextTable Command = "document-next-table"
|
|
DocumentPreviousTable Command = "document-previous-table"
|
|
DocumentNextUnvisitedLink Command = "document-next-unvisited-link"
|
|
DocumentPreviousUnvisitedLink Command = "document-previous-unvisited-link"
|
|
DocumentNextVisitedLink Command = "document-next-visited-link"
|
|
DocumentPreviousVisitedLink Command = "document-previous-visited-link"
|
|
DocumentNextClickable Command = "document-next-clickable"
|
|
DocumentPreviousClickable Command = "document-previous-clickable"
|
|
DocumentNextChunk Command = "document-next-chunk"
|
|
DocumentPreviousChunk Command = "document-previous-chunk"
|
|
DocumentCaretNextCharacter Command = "document-caret-next-character"
|
|
DocumentCaretPreviousCharacter Command = "document-caret-previous-character"
|
|
DocumentCaretNextWord Command = "document-caret-next-word"
|
|
DocumentCaretPreviousWord Command = "document-caret-previous-word"
|
|
DocumentCaretNextLine Command = "document-caret-next-line"
|
|
DocumentCaretPreviousLine Command = "document-caret-previous-line"
|
|
DocumentCaretLineStart Command = "document-caret-line-start"
|
|
DocumentCaretLineEnd Command = "document-caret-line-end"
|
|
DocumentCaretDocumentStart Command = "document-caret-document-start"
|
|
DocumentCaretDocumentEnd Command = "document-caret-document-end"
|
|
DocumentTableLeft Command = "document-table-left"
|
|
DocumentTableRight Command = "document-table-right"
|
|
DocumentTableUp Command = "document-table-up"
|
|
DocumentTableDown Command = "document-table-down"
|
|
DocumentTableFirst Command = "document-table-first"
|
|
DocumentTableLast Command = "document-table-last"
|
|
DocumentActivate Command = "document-activate"
|
|
ReadAll Command = "read-all"
|
|
DisableTentacles Command = "disable-tentacles"
|
|
Quit Command = "quit"
|
|
Preferences Command = "preferences"
|
|
ApplicationPreferences Command = "application-preferences"
|
|
ChooseApplicationPreferences Command = "choose-application-preferences"
|
|
PresentCellFormula Command = "present-cell-formula"
|
|
WhereAmI Command = "where-am-i"
|
|
PresentCharacterAttributes Command = "present-character-attributes"
|
|
PresentLastNotification Command = "present-last-notification"
|
|
PresentPreviousNotification Command = "present-previous-notification"
|
|
PresentNextNotification Command = "present-next-notification"
|
|
ShowNotificationList Command = "show-notification-list"
|
|
ObjectParent Command = "object-parent"
|
|
ObjectFirstChild Command = "object-first-child"
|
|
ObjectPreviousSibling Command = "object-previous-sibling"
|
|
ObjectNextSibling Command = "object-next-sibling"
|
|
ReviewWindowStart Command = "review-window-start"
|
|
ReviewWindowEnd Command = "review-window-end"
|
|
ReviewPreviousLine Command = "review-previous-line"
|
|
ReviewCurrentLine Command = "review-current-line"
|
|
ReviewNextLine Command = "review-next-line"
|
|
ReviewPreviousWord Command = "review-previous-word"
|
|
ReviewCurrentWord Command = "review-current-word"
|
|
ReviewSpellWord Command = "review-spell-word"
|
|
ReviewPhoneticWord Command = "review-phonetic-word"
|
|
ReviewNextWord Command = "review-next-word"
|
|
ReviewPreviousCharacter Command = "review-previous-character"
|
|
ReviewCurrentCharacter Command = "review-current-character"
|
|
ReviewPhoneticCharacter Command = "review-phonetic-character"
|
|
ReviewNextCharacter Command = "review-next-character"
|
|
ReviewWordAbove Command = "review-word-above"
|
|
ReviewWordBelow Command = "review-word-below"
|
|
ReviewLineEnd Command = "review-line-end"
|
|
)
|
|
|
|
type KeyboardLayout int
|
|
|
|
const (
|
|
DesktopKeyboardLayout KeyboardLayout = 1
|
|
LaptopKeyboardLayout KeyboardLayout = 2
|
|
)
|
|
|
|
const (
|
|
returnKeySymbol uint32 = 0xff0d
|
|
keypadEnterKeySymbol uint32 = 0xff8d
|
|
leftKeySymbol uint32 = 0xff51
|
|
upKeySymbol uint32 = 0xff52
|
|
rightKeySymbol uint32 = 0xff53
|
|
downKeySymbol uint32 = 0xff54
|
|
leftControlKeySymbol uint32 = 0xffe3
|
|
rightControlKeySymbol uint32 = 0xffe4
|
|
leftShiftKeySymbol uint32 = 0xffe1
|
|
rightShiftKeySymbol uint32 = 0xffe2
|
|
controlModifier uint16 = 1 << 2
|
|
)
|
|
|
|
type CommandChord struct {
|
|
Symbol uint32
|
|
Modifiers uint16
|
|
CthulhuModifier bool
|
|
}
|
|
|
|
type CommandBindings map[CommandChord]Command
|
|
|
|
// CommandEvent retains the initiating X server time across asynchronous delivery.
|
|
type CommandEvent struct {
|
|
Command Command
|
|
Timestamp uint32
|
|
Chord CommandChord
|
|
KeyName string
|
|
LearnModeStarted bool
|
|
UISession uint64
|
|
Text string
|
|
}
|
|
|
|
type LockState struct {
|
|
Name string
|
|
Locked bool
|
|
Timestamp uint32
|
|
}
|
|
|
|
func (state LockState) Text() string {
|
|
if state.Locked {
|
|
return state.Name + " on"
|
|
}
|
|
return state.Name + " off"
|
|
}
|
|
|
|
func DefaultCommandBindings(layout KeyboardLayout) CommandBindings {
|
|
bindings := CommandBindings{
|
|
{Symbol: 'a', Modifiers: 1, CthulhuModifier: true}: ShowActions,
|
|
{Symbol: leftKeySymbol, CthulhuModifier: true}: SelectPreviousSpeechSetting,
|
|
{Symbol: rightKeySymbol, CthulhuModifier: true}: SelectNextSpeechSetting,
|
|
{Symbol: downKeySymbol, CthulhuModifier: true}: DecreaseSpeechSetting,
|
|
{Symbol: upKeySymbol, CthulhuModifier: true}: IncreaseSpeechSetting,
|
|
{Symbol: 's', Modifiers: controlModifier | 1, CthulhuModifier: true}: SaveSpeechSettings,
|
|
{Symbol: 't', CthulhuModifier: true}: PresentTime,
|
|
{Symbol: 'c', Modifiers: 1, CthulhuModifier: true}: PresentClipboard,
|
|
{Symbol: 'h', Modifiers: controlModifier, CthulhuModifier: true}: ShowSpeechHistory,
|
|
{Symbol: 'd', Modifiers: 1, CthulhuModifier: true}: ToggleSpeechMonitor,
|
|
{Symbol: 's', CthulhuModifier: true}: CycleSpeechMode,
|
|
{Symbol: 'u', CthulhuModifier: true}: CycleProgressBarOutput,
|
|
{Symbol: 'v', Modifiers: 1, CthulhuModifier: true}: PresentVersion,
|
|
{Symbol: 'a', CthulhuModifier: true}: ToggleDocumentMode,
|
|
{Symbol: 'h', CthulhuModifier: true}: ToggleLearnMode,
|
|
{Symbol: 0xffbe, CthulhuModifier: true}: ToggleLearnMode,
|
|
{Symbol: 'h', Modifiers: 1, CthulhuModifier: true}: DocumentPreviousHeading,
|
|
{Symbol: 'k', CthulhuModifier: true, Modifiers: controlModifier}: DocumentNextLink,
|
|
{Symbol: 'k', Modifiers: controlModifier | 1, CthulhuModifier: true}: DocumentPreviousLink,
|
|
{Symbol: 'q', CthulhuModifier: true}: Quit,
|
|
{Symbol: 'q', Modifiers: 5, CthulhuModifier: true}: DisableTentacles,
|
|
{Symbol: 'n', CthulhuModifier: true}: PresentLastNotification,
|
|
{Symbol: 'n', Modifiers: controlModifier, CthulhuModifier: true}: ShowNotificationList,
|
|
{Symbol: ' ', CthulhuModifier: true}: Preferences,
|
|
{Symbol: ' ', Modifiers: controlModifier, CthulhuModifier: true}: ApplicationPreferences,
|
|
{Symbol: ' ', Modifiers: 1, CthulhuModifier: true}: ApplicationPreferences,
|
|
{Symbol: ' ', Modifiers: controlModifier | 1, CthulhuModifier: true}: ChooseApplicationPreferences,
|
|
{Symbol: 'f', CthulhuModifier: true}: PresentCharacterAttributes,
|
|
{Symbol: upKeySymbol, Modifiers: controlModifier, CthulhuModifier: true}: ObjectParent,
|
|
{Symbol: downKeySymbol, Modifiers: controlModifier, CthulhuModifier: true}: ObjectFirstChild,
|
|
{Symbol: leftKeySymbol, Modifiers: controlModifier, CthulhuModifier: true}: ObjectPreviousSibling,
|
|
{Symbol: rightKeySymbol, Modifiers: controlModifier, CthulhuModifier: true}: ObjectNextSibling,
|
|
}
|
|
if layout == DesktopKeyboardLayout {
|
|
bindings[CommandChord{Symbol: 'o', CthulhuModifier: true}] = OCRWindow
|
|
bindings[CommandChord{Symbol: 0xffab}] = ReadAll // KP_Add
|
|
bindings[CommandChord{Symbol: keypadEnterKeySymbol}] = WhereAmI
|
|
bindings[CommandChord{Symbol: '=', CthulhuModifier: true}] = PresentCellFormula
|
|
bindings[CommandChord{Symbol: 0xff95, CthulhuModifier: true}] = ReviewWindowStart
|
|
bindings[CommandChord{Symbol: 0xff9a, CthulhuModifier: true}] = ReviewWindowEnd
|
|
bindings[CommandChord{Symbol: 0xff96, CthulhuModifier: true}] = ReviewWordAbove
|
|
bindings[CommandChord{Symbol: 0xff98, CthulhuModifier: true}] = ReviewWordBelow
|
|
bindings[CommandChord{Symbol: 0xff9c, CthulhuModifier: true}] = ReviewLineEnd
|
|
} else if layout == LaptopKeyboardLayout {
|
|
bindings[CommandChord{Symbol: 'o', Modifiers: 1, CthulhuModifier: true}] = OCRWindow
|
|
bindings[CommandChord{Symbol: 'u', Modifiers: 1, CthulhuModifier: true}] = CycleProgressBarOutput
|
|
bindings[CommandChord{Symbol: ';', CthulhuModifier: true}] = ReadAll
|
|
bindings[CommandChord{Symbol: returnKeySymbol, CthulhuModifier: true}] = WhereAmI
|
|
bindings[CommandChord{Symbol: 'u', Modifiers: controlModifier, CthulhuModifier: true}] = ReviewWindowStart
|
|
bindings[CommandChord{Symbol: 'o', Modifiers: controlModifier, CthulhuModifier: true}] = ReviewWindowEnd
|
|
bindings[CommandChord{Symbol: 'j', Modifiers: controlModifier, CthulhuModifier: true}] = ReviewWordAbove
|
|
bindings[CommandChord{Symbol: 'l', Modifiers: controlModifier, CthulhuModifier: true}] = ReviewWordBelow
|
|
bindings[CommandChord{Symbol: 'm', Modifiers: controlModifier, CthulhuModifier: true}] = ReviewLineEnd
|
|
}
|
|
for _, binding := range []struct {
|
|
desktop, laptop uint32
|
|
command Command
|
|
}{
|
|
{0xff95, 'u', ReviewPreviousLine}, {0xff97, 'i', ReviewCurrentLine}, {0xff9a, 'o', ReviewNextLine},
|
|
{0xff96, 'j', ReviewPreviousWord}, {0xff9d, 'k', ReviewCurrentWord}, {0xff98, 'l', ReviewNextWord},
|
|
{0xff9c, 'm', ReviewPreviousCharacter}, {0xff99, ',', ReviewCurrentCharacter}, {0xff9b, '.', ReviewNextCharacter},
|
|
} {
|
|
if layout == DesktopKeyboardLayout {
|
|
bindings[CommandChord{Symbol: binding.desktop}] = binding.command
|
|
}
|
|
if layout == LaptopKeyboardLayout {
|
|
bindings[CommandChord{Symbol: binding.laptop, CthulhuModifier: true}] = binding.command
|
|
}
|
|
}
|
|
return bindings
|
|
}
|
|
|
|
// CommandRouter owns the pressed state of screen-reader modifiers and consumed
|
|
// command keys. Commands are emitted on press; native owners retain the grab
|
|
// independently until the chord releases, including during handler shutdown.
|
|
type CommandRouter struct {
|
|
now func() time.Time
|
|
lastTimePress time.Time
|
|
lastModePress time.Time
|
|
modePresses int
|
|
lastReviewPress time.Time
|
|
lastReviewChord CommandChord
|
|
reviewPresses int
|
|
modifiers map[uint32]bool
|
|
bindings CommandBindings
|
|
down map[uint16]bool
|
|
consumed map[uint16]bool
|
|
}
|
|
|
|
func NewCommandRouter(modifiers []uint32, bindings CommandBindings) *CommandRouter {
|
|
router := &CommandRouter{
|
|
now: time.Now,
|
|
modifiers: make(map[uint32]bool),
|
|
bindings: make(CommandBindings, len(bindings)),
|
|
down: make(map[uint16]bool),
|
|
consumed: make(map[uint16]bool),
|
|
}
|
|
for _, symbol := range modifiers {
|
|
router.modifiers[symbol] = true
|
|
}
|
|
for chord, command := range bindings {
|
|
router.bindings[chord] = command
|
|
}
|
|
return router
|
|
}
|
|
|
|
func (router *CommandRouter) Handle(symbol uint32, code uint16, modifiers uint16, pressed bool) (bool, Command) {
|
|
consume := false
|
|
if !pressed {
|
|
consume = router.down[code] || router.consumed[code]
|
|
delete(router.down, code)
|
|
delete(router.consumed, code)
|
|
} else if router.modifiers[symbol] {
|
|
router.down[code] = true
|
|
consume = true
|
|
} else if router.consumed[code] {
|
|
consume = true
|
|
} else {
|
|
chord := CommandChord{
|
|
Symbol: symbol,
|
|
Modifiers: modifiers &^ uint16(2|16),
|
|
CthulhuModifier: len(router.down) != 0,
|
|
}
|
|
if command := router.bindings[chord]; command != "" {
|
|
router.consumed[code] = true
|
|
command = router.review_press(command, chord)
|
|
if command == ToggleDocumentMode {
|
|
now := router.now()
|
|
if !router.lastModePress.IsZero() && now.Sub(router.lastModePress) >= 0 && now.Sub(router.lastModePress) <= 500*time.Millisecond {
|
|
router.modePresses++
|
|
} else {
|
|
router.modePresses = 1
|
|
}
|
|
router.lastModePress = now
|
|
switch router.modePresses {
|
|
case 2:
|
|
command = StickyFocusMode
|
|
case 3:
|
|
command = StickyBrowseMode
|
|
router.lastModePress = time.Time{}
|
|
}
|
|
} else {
|
|
router.lastModePress = time.Time{}
|
|
}
|
|
if command == PresentTime {
|
|
now := router.now()
|
|
if !router.lastTimePress.IsZero() && now.Sub(router.lastTimePress) >= 0 && now.Sub(router.lastTimePress) <= 500*time.Millisecond {
|
|
command = PresentDate
|
|
router.lastTimePress = time.Time{}
|
|
} else {
|
|
router.lastTimePress = now
|
|
}
|
|
} else {
|
|
router.lastTimePress = time.Time{}
|
|
}
|
|
return true, command
|
|
}
|
|
router.lastTimePress = time.Time{}
|
|
router.lastModePress = time.Time{}
|
|
router.lastReviewPress = time.Time{}
|
|
}
|
|
return consume, ""
|
|
}
|
|
|
|
func (router *CommandRouter) IsModifier(symbol uint32) bool {
|
|
switch symbol {
|
|
case leftControlKeySymbol, rightControlKeySymbol, leftShiftKeySymbol, rightShiftKeySymbol,
|
|
0xffe7, 0xffe8, 0xffe9, 0xffea, 0xffeb, 0xffec, 0xffed, 0xffee: // Meta, Alt, Super, Hyper.
|
|
return true
|
|
}
|
|
return router.modifiers[symbol]
|
|
}
|
|
|
|
// Chord reports the keys currently held before handling the next press.
|
|
func (router *CommandRouter) Chord(symbol uint32, modifiers uint16) CommandChord {
|
|
return CommandChord{Symbol: symbol, Modifiers: modifiers &^ uint16(2|16), CthulhuModifier: len(router.down) != 0}
|
|
}
|
|
|
|
func (router *CommandRouter) Reset() {
|
|
clear(router.down)
|
|
clear(router.consumed)
|
|
}
|
|
|
|
// Browse bindings are installed only while the runtime owns live web content.
|
|
func BrowseCommandBindings(layout KeyboardLayout) CommandBindings {
|
|
bindings := DefaultCommandBindings(layout)
|
|
caretBindings := CommandBindings{
|
|
{Symbol: leftKeySymbol, Modifiers: 1 | 8}: DocumentTableLeft,
|
|
{Symbol: rightKeySymbol, Modifiers: 1 | 8}: DocumentTableRight,
|
|
{Symbol: upKeySymbol, Modifiers: 1 | 8}: DocumentTableUp,
|
|
{Symbol: downKeySymbol, Modifiers: 1 | 8}: DocumentTableDown,
|
|
{Symbol: 0xff50, Modifiers: 1 | 8}: DocumentTableFirst,
|
|
{Symbol: 0xff57, Modifiers: 1 | 8}: DocumentTableLast,
|
|
{Symbol: rightKeySymbol}: DocumentCaretNextCharacter,
|
|
{Symbol: leftKeySymbol}: DocumentCaretPreviousCharacter,
|
|
{Symbol: rightKeySymbol, Modifiers: controlModifier}: DocumentCaretNextWord,
|
|
{Symbol: leftKeySymbol, Modifiers: controlModifier}: DocumentCaretPreviousWord,
|
|
{Symbol: downKeySymbol}: DocumentCaretNextLine,
|
|
{Symbol: upKeySymbol}: DocumentCaretPreviousLine,
|
|
{Symbol: 0xff50}: DocumentCaretLineStart,
|
|
{Symbol: 0xff57}: DocumentCaretLineEnd,
|
|
{Symbol: 0xff50, Modifiers: controlModifier}: DocumentCaretDocumentStart,
|
|
{Symbol: 0xff57, Modifiers: controlModifier}: DocumentCaretDocumentEnd,
|
|
{Symbol: returnKeySymbol}: DocumentActivate,
|
|
}
|
|
for chord, command := range caretBindings {
|
|
bindings[chord] = command
|
|
}
|
|
documentBindings := []struct {
|
|
symbol uint32
|
|
next Command
|
|
previous Command
|
|
}{
|
|
{'h', DocumentNextHeading, DocumentPreviousHeading},
|
|
{'k', DocumentNextLink, DocumentPreviousLink},
|
|
{'b', DocumentNextButton, DocumentPreviousButton},
|
|
{'x', DocumentNextCheckBox, DocumentPreviousCheckBox},
|
|
{'l', DocumentNextList, DocumentPreviousList},
|
|
{'s', DocumentNextSeparator, DocumentPreviousSeparator},
|
|
{'q', DocumentNextBlockQuote, DocumentPreviousBlockQuote},
|
|
{'c', DocumentNextComboBox, DocumentPreviousComboBox},
|
|
{'e', DocumentNextEntry, DocumentPreviousEntry},
|
|
{'f', DocumentNextFormField, DocumentPreviousFormField},
|
|
{'g', DocumentNextImage, DocumentPreviousImage},
|
|
{'m', DocumentNextLandmark, DocumentPreviousLandmark},
|
|
{'i', DocumentNextListItem, DocumentPreviousListItem},
|
|
{'d', DocumentNextLiveRegion, DocumentPreviousLiveRegion},
|
|
{'p', DocumentNextParagraph, DocumentPreviousParagraph},
|
|
{'r', DocumentNextRadioButton, DocumentPreviousRadioButton},
|
|
{'t', DocumentNextTable, DocumentPreviousTable},
|
|
{'u', DocumentNextUnvisitedLink, DocumentPreviousUnvisitedLink},
|
|
{'v', DocumentNextVisitedLink, DocumentPreviousVisitedLink},
|
|
{'a', DocumentNextClickable, DocumentPreviousClickable},
|
|
{'o', DocumentNextChunk, DocumentPreviousChunk},
|
|
}
|
|
for _, binding := range documentBindings {
|
|
bindings[CommandChord{Symbol: binding.symbol}] = binding.next
|
|
bindings[CommandChord{Symbol: binding.symbol, Modifiers: 1}] = binding.previous
|
|
}
|
|
headingLevels := []struct {
|
|
level int
|
|
next Command
|
|
previous Command
|
|
}{
|
|
{1, DocumentNextHeadingAtLevel1, DocumentPreviousHeadingAtLevel1},
|
|
{2, DocumentNextHeadingAtLevel2, DocumentPreviousHeadingAtLevel2},
|
|
{3, DocumentNextHeadingAtLevel3, DocumentPreviousHeadingAtLevel3},
|
|
{4, DocumentNextHeadingAtLevel4, DocumentPreviousHeadingAtLevel4},
|
|
{5, DocumentNextHeadingAtLevel5, DocumentPreviousHeadingAtLevel5},
|
|
{6, DocumentNextHeadingAtLevel6, DocumentPreviousHeadingAtLevel6},
|
|
}
|
|
for _, binding := range headingLevels {
|
|
symbol := uint32('0' + binding.level)
|
|
bindings[CommandChord{Symbol: symbol}] = binding.next
|
|
bindings[CommandChord{Symbol: symbol, Modifiers: 1}] = binding.previous
|
|
}
|
|
return bindings
|
|
}
|
|
|
|
// ReplaceBindings preserves multi-press timing across browse grab changes.
|
|
// Native owners call this only after every consumed key has been released.
|
|
func (router *CommandRouter) ReplaceBindings(bindings CommandBindings) {
|
|
router.bindings = make(CommandBindings, len(bindings))
|
|
for chord, command := range bindings {
|
|
router.bindings[chord] = command
|
|
}
|
|
}
|