-
Notifications
You must be signed in to change notification settings - Fork 1
/
map.go
138 lines (121 loc) · 3.05 KB
/
map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"github.com/fossegrim/midimap/lang"
"github.com/fossegrim/midimap/lang/mapping"
"github.com/micmonay/keybd_event"
"gitlab.com/gomidi/midi"
"gitlab.com/gomidi/midi/reader"
)
// mapCommandModifier corresponds to the map command modifier. args corresponds
// to the list of arguments listed on the command line after the map command
// modifier.
//
// For documentation about the map command modifier itself, see midimap(1).
func mapCommandModifier(args []string) error {
if len(args) != 2 {
return errUsage
}
mapName := args[1]
portNumber, err := parsePortNumber(args[0])
if err != nil {
return err
}
mappings, err := getMappingsFromMapName(mapName)
if err != nil {
return err
}
drv, err := newDriver()
if err != nil {
return err
}
defer drv.Close()
ins, err := drv.Ins()
if err != nil {
return err
}
in, err := getInByPortNumber(ins, portNumber)
if err != nil {
return err
}
err = in.Open()
if err != nil {
return err
}
defer in.Close()
kb, err := keybd_event.NewKeyBonding()
if err != nil {
if err.Error() == "permission error for /dev/uinput try cmd : sudo chmod +0666 /dev/uinput" {
return errors.New("insufficient permissions to simulate keypresses")
}
return err
}
rd := reader.New(
reader.NoLogger(),
reader.Each(func(pos *reader.Position, msg midi.Message) {
err := mapMIDIMessageToKeyPress(kb, mappings, msg)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}),
)
// I don't understand how and why this snippet works, but it does.
// I asked for an explanation in https://github.com/vipul-sharma20/midi-macro/issues/1.
// TODO: Read chapter 8 in gopl, which hopefully explains this
exit := make(chan string)
go rd.ListenTo(in)
for {
select {
case <-exit:
return nil
}
}
}
func mapMIDIMessageToKeyPress(kb keybd_event.KeyBonding, mappings []mapping.Mapping, msg midi.Message) (err error) {
for _, mapping := range mappings {
if matcherMatchesMessage(mapping.Matcher, msg) {
// NB: We iterate through all mappings regardless of if some earlier mapping matched. This is expected behaviour.
err = press(kb, mapping.Keycode)
if err != nil {
break
}
}
}
return
}
// getMappingsFromMapName parses a midimap-lang file with a name of mapName and retrieves its mappings.
// If an io-error occurs, the error is returned.
// If the parser fails at parsing some mapping, it describe the problem and moves on to the next mapping.
// No error is returned for parsing errors.
func getMappingsFromMapName(mapName string) (mappings []mapping.Mapping, err error) {
mapFile, err := os.Open(mapName)
if err != nil {
return
}
r := bufio.NewReader(mapFile)
for {
mapping, err := lang.NextMapping(r)
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "lang: %v\n", err)
}
mappings = append(mappings, mapping)
}
return
}
// press simulates pressing k on kb.
func press(kb keybd_event.KeyBonding, k int) (err error) {
kb.SetKeys(k)
err = kb.Launching()
if err != nil {
return
}
kb.Clear()
return
}