kopia lustrzana https://github.com/abourget/shuttle-go
Add OSC support. Enables piloting Ardour with the same software.
See updated sample config for how to use.pull/5/head
rodzic
28bab576e5
commit
675ce7f8a4
54
config.go
54
config.go
|
@ -4,8 +4,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hypebeast/go-osc/osc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var loadedConfiguration = &Config{}
|
var loadedConfiguration = &Config{}
|
||||||
|
@ -19,6 +23,7 @@ type AppConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MatchWindowTitles []string `json:"match_window_titles"`
|
MatchWindowTitles []string `json:"match_window_titles"`
|
||||||
SlowJog *int `json:"slow_jog"` // Time in millisecond to use slow jog
|
SlowJog *int `json:"slow_jog"` // Time in millisecond to use slow jog
|
||||||
|
Driver string `json:"driver"`
|
||||||
windowTitleRegexps []*regexp.Regexp
|
windowTitleRegexps []*regexp.Regexp
|
||||||
Bindings map[string]string `json:"bindings"`
|
Bindings map[string]string `json:"bindings"`
|
||||||
bindings []*deviceBinding
|
bindings []*deviceBinding
|
||||||
|
@ -45,11 +50,17 @@ func (ac *AppConfig) parse() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type deviceBinding struct {
|
type deviceBinding struct {
|
||||||
|
rawKey string
|
||||||
|
rawValue string
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
heldButtons map[int]bool
|
heldButtons map[int]bool
|
||||||
buttonDown int
|
buttonDown int
|
||||||
otherKey string
|
otherKey string
|
||||||
|
|
||||||
|
driver string
|
||||||
|
oscClient *osc.Client
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
holdButtons []string
|
holdButtons []string
|
||||||
pressButton string
|
pressButton string
|
||||||
|
@ -58,9 +69,32 @@ type deviceBinding struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AppConfig) parseBindings() error {
|
func (ac *AppConfig) parseBindings() error {
|
||||||
|
driverProtocol := "xdotool"
|
||||||
|
var oscClient *osc.Client
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ac.Driver == "":
|
||||||
|
case ac.Driver == "xdotool":
|
||||||
|
case strings.HasPrefix(ac.Driver, "osc://"):
|
||||||
|
addr, err := url.Parse(ac.Driver)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed parsing osc:// address: %s", err)
|
||||||
|
}
|
||||||
|
hostParts := strings.Split(addr.Host, ":")
|
||||||
|
if len(hostParts) != 2 {
|
||||||
|
return fmt.Errorf("please specify a port for the osc:// address")
|
||||||
|
}
|
||||||
|
port, _ := strconv.ParseInt(hostParts[1], 10, 32)
|
||||||
|
|
||||||
|
driverProtocol = "osc"
|
||||||
|
oscClient = osc.NewClient(hostParts[0], int(port))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(`invalid driver %q, use one of: "xdotool" (default), "osc://address:port"`, ac.Driver)
|
||||||
|
}
|
||||||
|
|
||||||
for key, value := range ac.Bindings {
|
for key, value := range ac.Bindings {
|
||||||
binding, description := bindingAndDescription(value)
|
binding, description := bindingAndDescription(driverProtocol, value)
|
||||||
newBinding := &deviceBinding{heldButtons: make(map[int]bool), original: binding, description: description}
|
newBinding := &deviceBinding{heldButtons: make(map[int]bool), rawKey: key, rawValue: value, original: binding, description: description, driver: driverProtocol, oscClient: oscClient}
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
input := strings.Split(key, "+")
|
input := strings.Split(key, "+")
|
||||||
|
@ -79,7 +113,7 @@ func (ac *AppConfig) parseBindings() error {
|
||||||
} else {
|
} else {
|
||||||
keyID := shuttleKeys[key]
|
keyID := shuttleKeys[key]
|
||||||
if keyID == 0 {
|
if keyID == 0 {
|
||||||
return fmt.Errorf("binding %q, expects a button press, not a shuttle or jog movement")
|
return fmt.Errorf("binding %q, expects a button press, not a shuttle or jog movement", key)
|
||||||
}
|
}
|
||||||
newBinding.heldButtons[keyID] = true
|
newBinding.heldButtons[keyID] = true
|
||||||
}
|
}
|
||||||
|
@ -102,16 +136,24 @@ func (ac *AppConfig) parseBindings() error {
|
||||||
|
|
||||||
ac.bindings = append(ac.bindings, newBinding)
|
ac.bindings = append(ac.bindings, newBinding)
|
||||||
|
|
||||||
|
if *debugMode {
|
||||||
fmt.Printf("BINDING: %#v\n", newBinding)
|
fmt.Printf("BINDING: %#v\n", newBinding)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var descriptionRE = regexp.MustCompile(`([^/]*)(\s*// *(.+))?`)
|
var xdoDescriptionRE = regexp.MustCompile(`([^/]*)(\s*// *(.+))?`)
|
||||||
|
var oscDescriptionRE = regexp.MustCompile(`([^#]*)(\s*# *(.+))?`)
|
||||||
|
|
||||||
func bindingAndDescription(input string) (string, string) {
|
func bindingAndDescription(protocol, input string) (string, string) {
|
||||||
matches := descriptionRE.FindStringSubmatch(input)
|
re := xdoDescriptionRE
|
||||||
|
if protocol == "osc" {
|
||||||
|
re = oscDescriptionRE
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := re.FindStringSubmatch(input)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
return input, ""
|
return input, ""
|
||||||
}
|
}
|
||||||
|
|
7
main.go
7
main.go
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var configFile = flag.String("config", filepath.Join(os.Getenv("HOME"), ".shuttle-go.json"), "Location to the .shuttle-go.json configuration")
|
var configFile = flag.String("config", filepath.Join(os.Getenv("HOME"), ".shuttle-go.json"), "Location to the .shuttle-go.json configuration")
|
||||||
|
var debugMode = flag.Bool("debug", false, "Show debug messages (like window titles)")
|
||||||
var logFile = flag.String("log-file", "", "Log to a file instead of stdout")
|
var logFile = flag.String("log-file", "", "Log to a file instead of stdout")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -54,9 +55,13 @@ func main() {
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("ready")
|
fmt.Println("Ready")
|
||||||
mapper := NewMapper(dev)
|
mapper := NewMapper(dev)
|
||||||
mapper.watcher = watcher
|
mapper.watcher = watcher
|
||||||
|
|
||||||
|
// IF there's an `osc` driver specified, launch an OSC listener too:
|
||||||
|
go listenOSCFeedback()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if err := mapper.Process(); err != nil {
|
if err := mapper.Process(); err != nil {
|
||||||
fmt.Println("Error processing input events (continuing):", err)
|
fmt.Println("Error processing input events (continuing):", err)
|
||||||
|
|
123
mapper.go
123
mapper.go
|
@ -4,10 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
evdev "github.com/gvalkov/golang-evdev"
|
evdev "github.com/gvalkov/golang-evdev"
|
||||||
|
"github.com/hypebeast/go-osc/osc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mapper receives events from the Shuttle devices, and maps (through
|
// Mapper receives events from the Shuttle devices, and maps (through
|
||||||
|
@ -77,9 +79,11 @@ func (m *Mapper) dispatch(evs []evdev.InputEvent) {
|
||||||
newShuttleVal := shuttleVal(evs)
|
newShuttleVal := shuttleVal(evs)
|
||||||
if m.state.shuttle != newShuttleVal {
|
if m.state.shuttle != newShuttleVal {
|
||||||
keyName := fmt.Sprintf("S%d", newShuttleVal)
|
keyName := fmt.Sprintf("S%d", newShuttleVal)
|
||||||
|
if *debugMode {
|
||||||
fmt.Println("SHUTTLE", keyName)
|
fmt.Println("SHUTTLE", keyName)
|
||||||
|
}
|
||||||
if err := m.EmitOther(keyName); err != nil {
|
if err := m.EmitOther(keyName); err != nil {
|
||||||
fmt.Println("Shuttle movement %q: %s\n", keyName, err)
|
fmt.Printf("Shuttle movement %q: %s\n", keyName, err)
|
||||||
}
|
}
|
||||||
m.state.shuttle = newShuttleVal
|
m.state.shuttle = newShuttleVal
|
||||||
}
|
}
|
||||||
|
@ -100,10 +104,12 @@ func (m *Mapper) dispatch(evs []evdev.InputEvent) {
|
||||||
m.state.buttonsHeld = heldButtons
|
m.state.buttonsHeld = heldButtons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *debugMode {
|
||||||
fmt.Println("---")
|
fmt.Println("---")
|
||||||
for _, ev := range evs {
|
for _, ev := range evs {
|
||||||
fmt.Printf("TYPE: %d\tCODE: %d\tVALUE: %d\n", ev.Type, ev.Code, ev.Value)
|
fmt.Printf("TYPE: %d\tCODE: %d\tVALUE: %d\n", ev.Type, ev.Code, ev.Value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Lock on configuration changes
|
// TODO: Lock on configuration changes
|
||||||
|
|
||||||
|
@ -131,7 +137,9 @@ func (m *Mapper) EmitOther(key string) error {
|
||||||
|
|
||||||
upperKey := strings.ToUpper(key)
|
upperKey := strings.ToUpper(key)
|
||||||
|
|
||||||
|
if *debugMode {
|
||||||
fmt.Println("EmitOther:", key)
|
fmt.Println("EmitOther:", key)
|
||||||
|
}
|
||||||
|
|
||||||
for _, binding := range conf.bindings {
|
for _, binding := range conf.bindings {
|
||||||
if binding.otherKey == upperKey {
|
if binding.otherKey == upperKey {
|
||||||
|
@ -148,7 +156,9 @@ func (m *Mapper) EmitKeys(modifiers map[int]bool, keyDown int) error {
|
||||||
return fmt.Errorf("No configuration for this Window")
|
return fmt.Errorf("No configuration for this Window")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *debugMode {
|
||||||
fmt.Println("Emit Keys", modifiers, reverseShuttleKeys[keyDown])
|
fmt.Println("Emit Keys", modifiers, reverseShuttleKeys[keyDown])
|
||||||
|
}
|
||||||
|
|
||||||
for _, binding := range conf.bindings {
|
for _, binding := range conf.bindings {
|
||||||
if reflect.DeepEqual(binding.heldButtons, modifiers) && binding.buttonDown == keyDown {
|
if reflect.DeepEqual(binding.heldButtons, modifiers) && binding.buttonDown == keyDown {
|
||||||
|
@ -161,60 +171,75 @@ func (m *Mapper) EmitKeys(modifiers map[int]bool, keyDown int) error {
|
||||||
|
|
||||||
func (m *Mapper) executeBinding(binding *deviceBinding) error {
|
func (m *Mapper) executeBinding(binding *deviceBinding) error {
|
||||||
time.Sleep(25 * time.Millisecond)
|
time.Sleep(25 * time.Millisecond)
|
||||||
|
switch binding.driver {
|
||||||
// cookie := xtest.FakeInputChecked(m.watcher.conn, 2, 0x7b00, 0, m.watcher.lastWindowID, 0, 0, 0x00)
|
case "xdotool", "":
|
||||||
// if err := cookie.Check(); err != nil {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// cookie = xtest.FakeInputChecked(m.watcher.conn, 3, 0x7b00, 0, m.watcher.lastWindowID, 0, 0, 0x00)
|
|
||||||
// return cookie.Check()
|
|
||||||
|
|
||||||
fmt.Println("xdotool key --clearmodifiers", binding.original)
|
fmt.Println("xdotool key --clearmodifiers", binding.original)
|
||||||
return exec.Command("xdotool", "key", "--clearmodifiers", binding.original).Run()
|
return exec.Command("xdotool", "key", "--clearmodifiers", binding.original).Run()
|
||||||
|
case "osc":
|
||||||
|
msgs := parseOSCMessages(binding.original)
|
||||||
|
if msgs == nil {
|
||||||
|
fmt.Printf("Failed parsing OSC binding for keys %q. Remember %q should start with an /\n", binding.rawKey, binding.rawValue)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg.Address == "/sleep" {
|
||||||
|
fmt.Println("Sleeping for", msg.Arguments[0].(float64), "seconds")
|
||||||
|
time.Sleep(time.Duration(msg.Arguments[0].(float64)*1000) * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println("Sending OSC message:", msg)
|
||||||
|
err := binding.oscClient.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// holdButtons := binding.holdButtons
|
func parseOSCMessages(multiInput string) (out []*osc.Message) {
|
||||||
// pressButton := binding.pressButton
|
inputs := strings.Split(multiInput, " + ")
|
||||||
|
for _, input := range inputs {
|
||||||
|
msg := parseOSCMessage(strings.TrimSpace(input))
|
||||||
|
if msg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out = append(out, msg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// fmt.Println("Executing bindings:", holdButtons, pressButton)
|
func parseOSCMessage(input string) *osc.Message {
|
||||||
|
fields := strings.Fields(input) // move to something like `sh` interpretation (or quoted strings) if needed
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// time.Sleep(10 * time.Millisecond)
|
if !strings.HasPrefix(fields[0], "/") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// for _, button := range holdButtons {
|
msg := osc.NewMessage(fields[0])
|
||||||
// fmt.Println("Key down", button)
|
for _, arg := range fields[1:] {
|
||||||
// time.Sleep(10 * time.Millisecond)
|
if val, err := strconv.ParseFloat(arg, 64); err == nil {
|
||||||
|
msg.Append(val)
|
||||||
// if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[button]); err != nil {
|
} else if val, err := strconv.ParseInt(arg, 10, 64); err == nil {
|
||||||
// return err
|
msg.Append(val)
|
||||||
// }
|
} else if arg == "true" {
|
||||||
// }
|
msg.Append(true)
|
||||||
|
} else if arg == "false" {
|
||||||
// time.Sleep(10 * time.Millisecond)
|
msg.Append(false)
|
||||||
|
} else if arg == "nil" {
|
||||||
// fmt.Println("Key press", pressButton)
|
msg.Append(nil)
|
||||||
// if err := m.virtualKeyboard.KeyDown(keyboardKeysUpper[pressButton]); err != nil {
|
} else if arg == "null" {
|
||||||
// return err
|
msg.Append(nil)
|
||||||
// }
|
} else {
|
||||||
|
msg.Append(arg)
|
||||||
// time.Sleep(10 * time.Millisecond)
|
}
|
||||||
|
}
|
||||||
// if err := m.virtualKeyboard.KeyUp(keyboardKeysUpper[pressButton]); err != nil {
|
return msg
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func jogVal(evs []evdev.InputEvent) int {
|
func jogVal(evs []evdev.InputEvent) int {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hypebeast/go-osc/osc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listenOSCFeedback() {
|
||||||
|
addr := "127.0.0.1:8000"
|
||||||
|
server := &osc.Server{Addr: addr}
|
||||||
|
|
||||||
|
server.Handle("*", func(msg *osc.Message) {
|
||||||
|
osc.PrintMessage(msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("Listening on :8000 for incoming OSC feedback")
|
||||||
|
server.ListenAndServe()
|
||||||
|
}
|
|
@ -3,20 +3,20 @@
|
||||||
{
|
{
|
||||||
"name": "Lightworks",
|
"name": "Lightworks",
|
||||||
"match_window_titles": [
|
"match_window_titles": [
|
||||||
"^Lightworks$", ".*"
|
"^Lightworks$"
|
||||||
],
|
],
|
||||||
"slow_jog": 200,
|
"slow_jog": 200,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"F1": "Escape // Switch viewer-recorder",
|
"F1": "Escape // Switch viewer-recorder",
|
||||||
|
|
||||||
"F3": "x // Delete",
|
|
||||||
"M1+F3": "z // Blackout",
|
|
||||||
|
|
||||||
"F2": "p // Clear Marks",
|
"F2": "p // Clear Marks",
|
||||||
"M1+F2": "Cyrillic_YA // Swap In-Out Marks",
|
"M1+F2": "Cyrillic_YA // Swap In-Out Marks",
|
||||||
|
|
||||||
|
"F3": "x // Delete",
|
||||||
|
"M1+F3": "z // Blackout",
|
||||||
"F4": "v // Insert",
|
"F4": "v // Insert",
|
||||||
"M1+F4": "b // Replace",
|
"M1+F4": "b // Replace",
|
||||||
|
"B2+F4": "f // Clipboard Insert",
|
||||||
|
"B2+M1+F4": "g // Clipboard Replace",
|
||||||
|
|
||||||
"M1+F5": "h // Home",
|
"M1+F5": "h // Home",
|
||||||
"F5": "a // Prev Clip",
|
"F5": "a // Prev Clip",
|
||||||
|
@ -40,9 +40,9 @@
|
||||||
"M1+M2+F3": "Tab",
|
"M1+M2+F3": "Tab",
|
||||||
"M1+M2+F4": "Tab",
|
"M1+M2+F4": "Tab",
|
||||||
|
|
||||||
"B2+F6": "G // Previous Tile in Bin",
|
"B2+F5": "G // Previous Tile in Bin",
|
||||||
|
"B2+F6": "J // Next Tile in Bin",
|
||||||
"B2+F7": "H // Load Tile into Viewer",
|
"B2+F7": "H // Load Tile into Viewer",
|
||||||
"B2+F8": "J // Next Tile in Bin",
|
|
||||||
|
|
||||||
"B4+F5": "hebrew_lamed // Live source 1",
|
"B4+F5": "hebrew_lamed // Live source 1",
|
||||||
"B4+F6": "hebrew_finalmem // Live source 2",
|
"B4+F6": "hebrew_finalmem // Live source 2",
|
||||||
|
@ -80,6 +80,82 @@
|
||||||
"S6": "B",
|
"S6": "B",
|
||||||
"S7": "N"
|
"S7": "N"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ardour",
|
||||||
|
"match_window_titles": [
|
||||||
|
"Ardour$"
|
||||||
|
],
|
||||||
|
"slow_jog": 200,
|
||||||
|
"driver": "osc://localhost:3819",
|
||||||
|
"__see_ardour_docs_for_actions": "http://manual.ardour.org/appendix/menu-actions-list/ and http://manual.ardour.org/using-control-surfaces/controlling-ardour-with-osc/osc-control/",
|
||||||
|
"bindings": {
|
||||||
|
"F1": "/access_action Transport/record-roll",
|
||||||
|
"F2": "/transport_stop + /sleep 0.25 + /access_action Editor/playhead-to-previous-region-boundary + /sleep 0.05 + /access_action Common/finish-range + /sleep 0.05 + /jump_seconds -0.1 + /sleep 0.05 + /access_action Common/start-range + /sleep 0.05 + /access_action Editor/editor-cut # Clears Marks on Lightworks",
|
||||||
|
"M1+F2": "Cyrillic_YA # Swap In-Out Marks",
|
||||||
|
|
||||||
|
"F3": "/access_action Editor/editor-cut + /access_action Editor/playhead-to-previous-region-boundary # Delete",
|
||||||
|
"M1+F3": "z # Blackout",
|
||||||
|
"F4": "v # Insert",
|
||||||
|
"M1+F4": "b # Replace",
|
||||||
|
"B2+F4": "f # Clipboard Insert",
|
||||||
|
"B2+M1+F4": "g # Clipboard Replace",
|
||||||
|
|
||||||
|
"M1+F5": "/goto_start # Home",
|
||||||
|
"F5": "/access_action Editor/playhead-to-previous-region-boundary # Prev Clip",
|
||||||
|
"F6": "/access_action Editor/playhead-to-next-region-boundary",
|
||||||
|
"M1+F6": "/goto_end # End",
|
||||||
|
|
||||||
|
"F7": "/access_action Common/start-range # Mark In",
|
||||||
|
"M1+F7": "/access_action Common/finish-range # Mark Out",
|
||||||
|
|
||||||
|
"F8": "/access_action Common/finish-range # Play Backwards",
|
||||||
|
"F9": "/transport_play # Play",
|
||||||
|
|
||||||
|
"M2+F1": "q",
|
||||||
|
"M2+F2": "w",
|
||||||
|
"M2+F3": "e",
|
||||||
|
"M2+F4": "r",
|
||||||
|
"M2+F9": "Tab",
|
||||||
|
"M1+M2+F1": "Tab",
|
||||||
|
"M1+M2+F2": "Tab",
|
||||||
|
"M1+M2+F3": "Tab",
|
||||||
|
"M1+M2+F4": "Tab",
|
||||||
|
|
||||||
|
"B2+F5": "G # Previous Tile in Bin",
|
||||||
|
"B2+F6": "J # Next Tile in Bin",
|
||||||
|
"B2+F7": "H # Load Tile into Viewer",
|
||||||
|
|
||||||
|
"B1+F1": "1 # Toggle V1",
|
||||||
|
"B1+F2": "2 # Toggle V2",
|
||||||
|
"B1+F3": "Ctrl+3 # Toggle V3",
|
||||||
|
"B1+F4": "Ctrl+0 # Toggle All Tracks",
|
||||||
|
"B1+F5": "3 # Toggle A1",
|
||||||
|
"B1+F6": "4 # Toggle A2",
|
||||||
|
"B1+F7": "5 # Toggle A3",
|
||||||
|
"B1+F8": "6 # Toggle A4",
|
||||||
|
"B1+F9": "7 # Toggle A5",
|
||||||
|
|
||||||
|
"JogL": "parenleft",
|
||||||
|
"JogR": "parenright",
|
||||||
|
"SlowJogL": "comma",
|
||||||
|
"SlowJogR": "period",
|
||||||
|
"S-7": "/set_transport_speed -8.0",
|
||||||
|
"S-6": "/set_transport_speed -4.0",
|
||||||
|
"S-5": "/set_transport_speed -2.0",
|
||||||
|
"S-4": "/set_transport_speed -1.0",
|
||||||
|
"S-3": "/set_transport_speed -0.5",
|
||||||
|
"S-2": "/set_transport_speed -0.25",
|
||||||
|
"S-1": "/set_transport_speed -0.1",
|
||||||
|
"S0": "/transport_stop",
|
||||||
|
"S1": "/set_transport_speed 0.1",
|
||||||
|
"S2": "/set_transport_speed 0.25",
|
||||||
|
"S3": "/set_transport_speed 0.5",
|
||||||
|
"S4": "/set_transport_speed 1.0",
|
||||||
|
"S5": "/set_transport_speed 2.0",
|
||||||
|
"S6": "/set_transport_speed 4.0",
|
||||||
|
"S7": "/set_transport_speed 8.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
10
watch.go
10
watch.go
|
@ -100,12 +100,20 @@ func (w *watcher) loadWindowConfiguration(windowName string) {
|
||||||
|
|
||||||
for _, conf := range loadedConfiguration.Apps {
|
for _, conf := range loadedConfiguration.Apps {
|
||||||
for _, re := range conf.windowTitleRegexps {
|
for _, re := range conf.windowTitleRegexps {
|
||||||
|
if *debugMode {
|
||||||
|
fmt.Println("Testing title:", windowName)
|
||||||
|
}
|
||||||
if re.MatchString(windowName) {
|
if re.MatchString(windowName) {
|
||||||
|
fmt.Printf("Switching configuration for app %q\n", conf.Name)
|
||||||
currentConfiguration = conf
|
currentConfiguration = conf
|
||||||
fmt.Printf("Applying configuration for app %q\n", conf.Name)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !*debugMode {
|
||||||
currentConfiguration = nil
|
currentConfiguration = nil
|
||||||
|
} else {
|
||||||
|
fmt.Println("Keeping previous config even if window changed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue