-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
280 lines (240 loc) · 8.61 KB
/
main.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package main
import (
// Standard Libraries
// "fmt"
"time"
"strings"
"os"
"os/exec"
// Keybinds
"github.com/jezek/xgbutil"
keybinds "github.com/jezek/xgbutil/keybind"
"github.com/jezek/xgbutil/xevent"
)
func run(currentMode string) {
// Get the X Server
X, err := xgbutil.NewConn()
if err != nil { panic(err) }
// Get the home directory
home, err := os.UserHomeDir()
if err != nil { panic(err) }
// Get config
modes := setupModes() // setupModes located in config.go
// Create special list for special keys to use
// TODO: I probably shouldnt use a map for this since I only need one value,
// not a key and a value
special := map[string]interface{} {}
// Initialize Keybindings
keybinds.Initialize(X)
// Nest
convertStringToShellCmd := func(home string, str string) func() {
// Allow usage of home directory
replacer := strings.NewReplacer("~", home)
str = replacer.Replace(str)
// Make readable to exec.Command,
// then convert to function & return
splitStr := strings.Split(str, " ")
execString := func() { exec.Command(splitStr[0], splitStr[1:]...).Run() }
return execString
}
makeSpecialKeySet := func(specialKey string, normalKey string, doubleClick bool, doubleClickDelay int) {
// example:
// "{mod4:z}-mod4-x": func() { fmt.Println("mod4-z = special key, mod4-x = normal key") }
// Note that due to limitations the special key's seperator has to be different than
// the normal key's seperator
// How this works:
// pressConn and releaseConn place/remove the normal key from the specials map
// when you press or release the special key on your keyboard
//
// while the normal key is in the specials map it can be detected when linkConn
// is ran, linkConn is just there to remove the normallKey from the map in case
// releaseConn can't find it, the actual code checking the specials map is in the
// conditionsMet function
// Add function to specialFuncs map upon press
pressConn := keybinds.KeyPressFun(func(X *xgbutil.XUtil, e xevent.KeyPressEvent) {
special[normalKey] = ""
})
// Remove function from specialFuncs map upon release
releaseConn := keybinds.KeyReleaseFun(func(X *xgbutil.XUtil, e xevent.KeyReleaseEvent) {
if _, ok := special[normalKey]; ok {
delete(special, normalKey)
_ = 1 // avoid "declared but not used"
}
})
// Link to the normal key, when this is pressed the special key has to be pressed
// again for this to work again, because of limitations in xgbutil
linkConn := keybinds.KeyPressFun(func(X *xgbutil.XUtil, e xevent.KeyPressEvent) {
if _, ok := special[normalKey]; ok {
go func() {
if doubleClick == true {
// 5 ms window for the run function to detect the special key
// This is probably not the best way to do this but i'll probs find a
// better way later
time.Sleep(time.Duration(doubleClickDelay + 5) * time.Millisecond)
}
delete(special, normalKey)
_ = 1 // avoid "declared but not used"
}()
}
})
// Connect to bindings
errPress := pressConn.Connect(X, X.RootWin(), specialKey, true)
errRelease := releaseConn.Connect(X, X.RootWin(), specialKey, true)
errLink := linkConn.Connect(X, X.RootWin(), normalKey, true)
// Handle Errors
if errPress != nil { panic(errPress) }
if errRelease != nil { panic(errRelease) }
if errLink != nil { panic(errLink) }
}
// Function to check if conditions are met to run the keybind's command
conditionsMet := func(isSpecial bool, special map[string]interface{}, attachSettings map[string]interface{}, run func(), currentMode string, modeName string) {
// checks whethr there is a special key situation, if there is
// it'll also check if it can run, if there is no special key situation
// it will check if something else has a special key situation with this
// key as a part of the set
if isSpecial == true {
if _, ok := special[attachSettings["normalKey"].(string)]; ok {
go run()
_ = 1 // avoid "declared but not used"
}
} else {
go run()
// FIXME
/*
if canRun, ok := specialFuncs[attachSettings[keybind].(string)]; ok {
fmt.Println(canRun)
} else {
go run()
*/
}
}
// Cycle through modes & set the default if its there,
// after that, cycle through the mode's keybindings
// and setup all the keybindings
go func() {
for modeName, mode := range modes {
for keybind, toRun := range mode.(map[string]interface{}) {
// Attach settings will be changed depending on the command as the code executes
attachSettings := map[string]interface{} {
"doubleClick": false,
"doubleClickDelay": 190,
"isSpecial": false,
"specialKey": "",
"normalKey": "",
}
// Get command the user is looking to run (from toRun)
var run func()
var run2 func() // run2 is ONLY used for double clicks
switch toRun.(type) {
// Double Clicks & One Clicks:
case []interface{}:
attachSettings["doubleClick"] = true
mapItem1 := toRun.([]interface{})[0]
mapItem2 := toRun.([]interface{})[1]
// One Click:
switch mapItem1.(type) {
case string:
run = convertStringToShellCmd(home, mapItem1.(string))
case func():
run = mapItem1.(func())
}
// Double Click:
switch mapItem2.(type) {
case string:
run2 = convertStringToShellCmd(home, mapItem2.(string))
case func():
run2 = mapItem2.(func())
}
// Normal Clicks:
case string:
attachSettings["doubleClick"] = false
run = convertStringToShellCmd(home, toRun.(string))
case func():
attachSettings["doubleClick"] = false
run = toRun.(func())
}
// Split keybind to take a look at each key (to look for special keys)
splitKeybind := strings.Split(keybind, "-") // seperator right now is -
for _, key := range splitKeybind {
if strings.HasPrefix(key, "{") && strings.HasSuffix(key, "}") {
// This means there is a special key situation that must be added
// to attachSettings
specialKey := strings.Replace(key, "{", "", 1) // remove prefix
specialKey = strings.Replace(specialKey, "}", "", 1) // remove suffix
specialKey = strings.Replace(specialKey, ":", "-", 1) // replace seperator
normalKey := strings.Replace(keybind, key + "-", "", 1) // remove special key from keybind
attachSettings["isSpecial"] = true
attachSettings["specialKey"] = specialKey
attachSettings["normalKey"] = normalKey
keybind = normalKey
makeSpecialKeySet(specialKey, normalKey, attachSettings["doubleClick"].(bool), attachSettings["doubleClickDelay"].(int))
_ = 1 // avoid "declared but not used"
}
}
// Attach
alwaysModeName := modeName
timesSent := 0
// Any other checks should be moved to the conditionsMet function
keybinds.KeyPressFun(func(X *xgbutil.XUtil, e xevent.KeyPressEvent) {
// Check mode
if currentMode == alwaysModeName {
// Double Click Handler
if attachSettings["doubleClick"] == true {
if timesSent == 0 {
timesSent++
go func() {
time.Sleep(time.Duration(attachSettings["doubleClickDelay"].(int)) * time.Millisecond)
if timesSent > 1 { // Double Click:
timesSent = 0
// this is just here to reset, the actual double click function
// runs a few lines down (the advantage to putting it there
// instead of here is to avoid the delay on running the double
// click function, however there is still no way to completely
// remove the delay for the one click function at the moment)
} else { // One Click:
timesSent = 0
conditionsMet(attachSettings["isSpecial"].(bool), special, attachSettings, run, currentMode, alwaysModeName)
}
}()
} else if timesSent == 1 {
timesSent++
conditionsMet(attachSettings["isSpecial"].(bool), special, attachSettings, run2, currentMode, alwaysModeName)
}
// Normal Click Handler
} else {
conditionsMet(attachSettings["isSpecial"].(bool), special, attachSettings, run, currentMode, alwaysModeName)
}
}
}).Connect(X, X.RootWin(), keybind, true)
}
}
}()
// Start main event loop
xevent.Main(X)
}
// CLI Stuff
func main() {
// Variables
currentMode := "default"
// Dumb way to get args but honestly i cant think of anything else
args := os.Args
arg1 := ""
arg2 := ""
switch len(args) {
case 3:
arg1 = args[1]
arg2 = args[2]
case 2:
arg1 = args[1]
default:
return
}
// Connect args to functions
if arg1 != "" {
switch args[1] {
case "run": run(currentMode)
case "stop": exec.Command("killall", "turboin").Run()
case "mode": if arg2 != "" { }
}
}
}