diff --git a/README.md b/README.md index cb5bbe6..33d5bc0 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,22 @@ Buttons layout on the Contour Design Shuttle Pro v2: See + + +TODO +---- + +* Fix up timings, make sure we properly support shortcuts with + Ctrl+Shift and it doesn't clog the program. Perhaps optimize and + keep certain keys pressed, until not needed anymore. Especially + using the Jog and Shuttle. + +* Make sure we have a solution to ignore the device as a generic HID + under Ubuntu. We can't have mouse clicks on top of our bindings! + +* Check udev, DISPLAY=:0.0 to start ? + * Retry ? Check the error message going out. + +* Try the xdotool with the latest bindings, XTest-based. + * Use xgb's `xtest` package and send the FakeInput directly there.. should work + a lot better. diff --git a/config.go b/config.go index d2ffd28..a9e4c29 100644 --- a/config.go +++ b/config.go @@ -18,6 +18,7 @@ type Config struct { type AppConfig struct { Name string `json:"name"` MatchWindowTitles []string `json:"match_window_titles"` + SlowJog int `json:"slow_jog"` // Time in millisecond to use slow jog windowTitleRegexps []*regexp.Regexp Bindings map[string]string `json:"bindings"` bindings []*deviceBinding @@ -52,11 +53,12 @@ type deviceBinding struct { // Output holdButtons []string pressButton string + original string } func (ac *AppConfig) parseBindings() error { for key, value := range ac.Bindings { - newBinding := &deviceBinding{heldButtons: make(map[int]bool)} + newBinding := &deviceBinding{heldButtons: make(map[int]bool), original: value} // Input input := strings.Split(key, "+") @@ -82,19 +84,19 @@ func (ac *AppConfig) parseBindings() error { } // Output - output := strings.Split(value, "+") - for idx, part := range output { - cleanPart := strings.TrimSpace(part) - buttonName := strings.ToUpper(cleanPart) - if keyboardKeysUpper[buttonName] == 0 { - return fmt.Errorf("keyboard key unknown: %q", cleanPart) - } - if idx == len(output)-1 { - newBinding.pressButton = buttonName - } else { - newBinding.holdButtons = append(newBinding.holdButtons, buttonName) - } - } + // output := strings.Split(value, "+") + // for idx, part := range output { + // cleanPart := strings.TrimSpace(part) + // buttonName := strings.ToUpper(cleanPart) + // if keyboardKeysUpper[buttonName] == 0 { + // return fmt.Errorf("keyboard key unknown: %q", cleanPart) + // } + // if idx == len(output)-1 { + // newBinding.pressButton = buttonName + // } else { + // newBinding.holdButtons = append(newBinding.holdButtons, buttonName) + // } + // } ac.bindings = append(ac.bindings, newBinding) diff --git a/definitions.go b/definitions.go index fc840ed..788b049 100644 --- a/definitions.go +++ b/definitions.go @@ -21,126 +21,131 @@ var shuttleKeys = map[string]int{ } var otherShuttleKeys = map[string]bool{ - "S-7": true, - "S-6": true, - "S-5": true, - "S-4": true, - "S-3": true, - "S-2": true, - "S-1": true, - "S0": true, - "S1": true, - "S2": true, - "S3": true, - "S4": true, - "S5": true, - "S6": true, - "S7": true, - "JogL": true, - "JogR": true, + "S-7": true, + "S-6": true, + "S-5": true, + "S-4": true, + "S-3": true, + "S-2": true, + "S-1": true, + "S0": true, + "S1": true, + "S2": true, + "S3": true, + "S4": true, + "S5": true, + "S6": true, + "S7": true, + "JogL": true, + "JogR": true, + "SlowJogL": true, + "SlowJogR": true, } var keyboardKeys = map[string]int{ - "Esc": 1, - "1": 2, - "2": 3, - "3": 4, - "4": 5, - "5": 6, - "6": 7, - "7": 8, - "8": 9, - "9": 10, - "0": 11, - "Minus": 12, - "-": 12, - "Equal": 13, - "=": 13, - "Backspace": 14, - "Tab": 15, - "Q": 16, - "W": 17, - "E": 18, - "R": 19, - "T": 20, - "Y": 21, - "U": 22, - "I": 23, - "O": 24, - "P": 25, - "LeftBrace": 26, - "RightBrace": 27, - "{": 26, - "}": 27, - "Enter": 28, - "LeftCtrl": 29, - "Ctrl": 29, - "A": 30, - "S": 31, - "D": 32, - "F": 33, - "G": 34, - "H": 35, - "J": 36, - "K": 37, - "L": 38, - "Semicolon": 39, - ";": 39, - "Apostrophe": 40, - "'": 40, - "Grave": 41, - "LeftShift": 42, - "Shift": 42, - "Backslash": 43, - "\\": 43, - "Z": 44, - "X": 45, - "C": 46, - "V": 47, - "B": 48, - "N": 49, - "M": 50, - "Comma": 51, - ",": 51, - "Dot": 52, - ".": 52, - "Slash": 53, - "/": 53, - "RightShift": 54, - "RShift": 54, - "KPAsterisk": 55, - "*": 55, - "LeftAlt": 56, - "Alt": 56, - "Space": 57, - "CapsLock": 58, - "F1": 59, - "F2": 60, - "F3": 61, - "F4": 62, - "F5": 63, - "F6": 64, - "F7": 65, - "F8": 66, - "F9": 67, - "F10": 68, - "NumLock": 69, - "ScrollLock": 70, - "KP7": 71, - "KP8": 72, - "KP9": 73, - "KPMinus": 74, - "KP4": 75, - "KP5": 76, - "KP6": 77, - "KPPlus": 78, - "KP1": 79, - "KP2": 80, - "KP3": 81, - "KP0": 82, - "KPDot": 83, - "F11": 87, - "F12": 88, + "Esc": 1, + "1": 2, + "2": 3, + "3": 4, + "4": 5, + "5": 6, + "6": 7, + "7": 8, + "8": 9, + "9": 10, + "0": 11, + "Minus": 12, + "-": 12, + "Equal": 13, + "=": 13, + "Backspace": 14, + "Tab": 15, + "Q": 16, + "W": 17, + "E": 18, + "R": 19, + "T": 20, + "Y": 21, + "U": 22, + "I": 23, + "O": 24, + "P": 25, + "LeftBrace": 26, + "RightBrace": 27, + "{": 26, + "}": 27, + "Enter": 28, + "LeftCtrl": 29, + "Ctrl": 29, + "A": 30, + "S": 31, + "D": 32, + "F": 33, + "G": 34, + "H": 35, + "J": 36, + "K": 37, + "L": 38, + "Semicolon": 39, + ";": 39, + "Apostrophe": 40, + "'": 40, + "Grave": 41, + "LeftShift": 42, + "Shift": 42, + "Backslash": 43, + "\\": 43, + "Z": 44, + "X": 45, + "C": 46, + "V": 47, + "B": 48, + "N": 49, + "M": 50, + "Comma": 51, + ",": 51, + "Dot": 52, + ".": 52, + "Slash": 53, + "/": 53, + "RightShift": 54, + "RShift": 54, + "KPAsterisk": 55, + "*": 55, + "LeftAlt": 56, + "Alt": 56, + "Space": 57, + "CapsLock": 58, + "F1": 59, + "F2": 60, + "F3": 61, + "F4": 62, + "F5": 63, + "F6": 64, + "F7": 65, + "F8": 66, + "F9": 67, + "F10": 68, + "NumLock": 69, + "ScrollLock": 70, + "KP7": 71, + "KP8": 72, + "KP9": 73, + "KPMinus": 74, + "KP4": 75, + "KP5": 76, + "KP6": 77, + "KPPlus": 78, + "KP1": 79, + "KP2": 80, + "KP3": 81, + "KP0": 82, + "KPDot": 83, + "F11": 87, + "F12": 88, + + "Henkan": 92, + "KPEnter": 96, "RightCtrl": 97, "RCtrl": 97, @@ -280,14 +285,14 @@ var keyboardKeys = map[string]int{ "Micmute": 248, /*Mute/UnmuteTheMicrophone*/ } -//var reverseShuttleKeys map[int]string +var reverseShuttleKeys = map[int]string{} var keyboardKeysUpper = map[string]int{} var otherShuttleKeysUpper = map[string]bool{} func init() { - // for k, v := range shuttleKeys { - // reverseShuttleKeys[v] = k - // } + for k, v := range shuttleKeys { + reverseShuttleKeys[v] = k + } for k, v := range keyboardKeys { keyboardKeysUpper[strings.ToUpper(k)] = v } diff --git a/main.go b/main.go index 39bc1e6..1770f2b 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ func main() { fmt.Println("ready") mapper := NewMapper(vk, dev) + mapper.watcher = watcher for { if err := mapper.Process(); err != nil { fmt.Println("Error processing input events (continuing):", err) diff --git a/mapper.go b/mapper.go index 077451f..cc1cc6c 100644 --- a/mapper.go +++ b/mapper.go @@ -2,8 +2,10 @@ package main import ( "fmt" + "os/exec" "reflect" "strings" + "time" "github.com/bendahl/uinput" evdev "github.com/gvalkov/golang-evdev" @@ -15,12 +17,14 @@ type Mapper struct { virtualKeyboard uinput.Keyboard inputDevice *evdev.InputDevice state buttonsState + watcher *watcher } type buttonsState struct { jog int shuttle int buttonsHeld map[int]bool + lastJog time.Time } func NewMapper(virtualKeyboard uinput.Keyboard, inputDevice *evdev.InputDevice) *Mapper { @@ -39,7 +43,6 @@ func (m *Mapper) Process() error { return err } - fmt.Println("---") m.dispatch(evs) return nil @@ -49,17 +52,27 @@ func (m *Mapper) dispatch(evs []evdev.InputEvent) { newJogVal := jogVal(evs) if m.state.jog != newJogVal { if m.state.jog != -1 { + if m.state.lastJog.IsZero() { + m.state.lastJog = time.Now() + } + + slow := "" + if time.Since(m.state.lastJog) > slowJogTiming() { + slow = "Slow" + } // Trigger JL or JR if we're advancing or not.. delta := newJogVal - m.state.jog if (delta > 0 || delta < -200) && (delta < 200) { - if err := m.EmitOther("JogR"); err != nil { + if err := m.EmitOther(slow + "JogR"); err != nil { fmt.Println("Jog right:", err) } } else { - if err := m.EmitOther("JogL"); err != nil { + if err := m.EmitOther(slow + "JogL"); err != nil { fmt.Println("Jog left:", err) } } + + m.state.lastJog = time.Now() } m.state.jog = newJogVal } @@ -67,6 +80,7 @@ func (m *Mapper) dispatch(evs []evdev.InputEvent) { newShuttleVal := shuttleVal(evs) if m.state.shuttle != newShuttleVal { keyName := fmt.Sprintf("S%d", newShuttleVal) + fmt.Println("SHUTTLE", keyName) if err := m.EmitOther(keyName); err != nil { fmt.Println("Shuttle movement %q: %s\n", keyName, err) } @@ -89,12 +103,24 @@ func (m *Mapper) dispatch(evs []evdev.InputEvent) { m.state.buttonsHeld = heldButtons } - //fmt.Printf("TYPE: %d\tCODE: %d\tVALUE: %d\n", ev.Type, ev.Code, ev.Value) + fmt.Println("---") + for _, ev := range evs { + fmt.Printf("TYPE: %d\tCODE: %d\tVALUE: %d\n", ev.Type, ev.Code, ev.Value) + } + // TODO: Lock on configuration changes return } +func slowJogTiming() time.Duration { + conf := currentConfiguration + if conf == nil { + return 200 * time.Millisecond + } + return time.Duration(conf.SlowJog) * time.Millisecond +} + func (m *Mapper) EmitOther(key string) error { conf := currentConfiguration if conf == nil { @@ -103,9 +129,11 @@ func (m *Mapper) EmitOther(key string) error { upperKey := strings.ToUpper(key) + fmt.Println("EmitOther:", key) + for _, binding := range conf.bindings { if binding.otherKey == upperKey { - return m.executeBinding(binding.holdButtons, binding.pressButton) + return m.executeBinding(binding) } } @@ -118,33 +146,63 @@ func (m *Mapper) EmitKeys(modifiers map[int]bool, keyDown int) error { return fmt.Errorf("No configuration for this Window") } + fmt.Println("Emit Keys", modifiers, reverseShuttleKeys[keyDown]) + for _, binding := range conf.bindings { if reflect.DeepEqual(binding.heldButtons, modifiers) && binding.buttonDown == keyDown { - return m.executeBinding(binding.holdButtons, binding.pressButton) + return m.executeBinding(binding) } } return fmt.Errorf("No binding for these keys") } -func (m *Mapper) executeBinding(holdButtons []string, pressButton string) error { +func (m *Mapper) executeBinding(binding *deviceBinding) error { + holdButtons := binding.holdButtons + pressButton := binding.pressButton + + //xtest.FakeInputChecked(m.watcher.conn, m.watcher.rootWin) + fmt.Println("xdotool key --clearmodifiers", binding.original) + return exec.Command("xdotool", "key", "--clearmodifiers", binding.original).Run() + fmt.Println("Executing bindings:", holdButtons, pressButton) + + time.Sleep(10 * time.Millisecond) + for _, button := range holdButtons { + fmt.Println("Key down", button) + time.Sleep(10 * time.Millisecond) + if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[button]); err != nil { return err } } - if err := m.virtualKeyboard.KeyPress(keyboardKeysUpper[pressButton]); err != nil { + time.Sleep(10 * time.Millisecond) + + fmt.Println("Key press", pressButton) + if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[pressButton]); err != nil { return err } + time.Sleep(10 * time.Millisecond) + + if err := m.virtualKeyboard.KeyUp(keyboardKeysUpper[pressButton]); err != nil { + return err + } + + time.Sleep(10 * time.Millisecond) + for _, button := range holdButtons { + fmt.Println("Key up", button) + time.Sleep(10 * time.Millisecond) if err := m.virtualKeyboard.KeyUp(keyboardKeysUpper[button]); err != nil { return err } } + time.Sleep(50 * time.Millisecond) + return nil } @@ -157,13 +215,16 @@ func jogVal(evs []evdev.InputEvent) int { return 0 } -func shuttleVal(evs []evdev.InputEvent) int { - for _, ev := range evs { +func shuttleVal(evs []evdev.InputEvent) (out int) { + for idx, ev := range evs { + if ev.Type == 0 && idx != len(evs)-1 { + out = 0 + } if ev.Type == 2 && ev.Code == 8 { - return int(ev.Value) + out = int(ev.Value) } } - return 0 + return } func buttonVals(current map[int]bool, ev evdev.InputEvent) (out map[int]bool, lastDown int) {