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
local cfg = UI.Schema({
layer = UI.Keybind("F6", { label = "Hold for a gesture", tab = "Controls" }),
cancel = UI.Keybind("Escape", { label = "Cancel gesture", tab = "Controls" }),
editing = UI.Keybind(
"1",
{ label = "Select Editing bank", tab = "Controls" }
),
browsing = UI.Keybind(
"2",
{ label = "Select Browsing bank", tab = "Controls" }
),
media = UI.Keybind("3", { label = "Select Media bank", tab = "Controls" }),
bank = UI.Select(
"Editing",
{ "Editing", "Browsing", "Media" },
{ label = "Initial bank", tab = "Gesture" }
),
threshold = UI.Slider(40, {
min = 5,
max = 1000,
step = 5,
suffix = " counts",
label = "Minimum stroke",
tab = "Gesture",
}),
dominance = UI.Slider(1.5, {
min = 1.1,
max = 3,
step = 0.1,
label = "Required axis dominance",
tab = "Gesture",
}),
})
local primary = if _REBIND.platform == "macos" then "LWin" else "LCtrl"
local BANKS = {
Editing = {
left = primary .. "+Z",
right = if _REBIND.platform == "macos" then "LWin+LShift+Z" else "LCtrl+Y",
up = primary .. "+C",
down = primary .. "+V",
},
Browsing = {
left = if _REBIND.platform == "macos" then "LWin+[" else "LAlt+Left",
right = if _REBIND.platform == "macos" then "LWin+]" else "LAlt+Right",
up = "LCtrl+LShift+Tab",
down = "LCtrl+Tab",
},
Media = {
left = "MediaPrev",
right = "MediaNext",
up = "VolumeUp",
down = "VolumeDown",
},
}
local keys, used = {}, {}
local allowed = { Escape = true, Space = true, CapsLock = true, Menu = true }
for n = 1, 12 do
allowed["F" .. n] = true
end
for n = 65, 90 do
allowed[string.char(n)] = true
end
for n = 0, 9 do
allowed[tostring(n)] = true
end
for _, name in { "layer", "cancel", "editing", "browsing", "media" } do
local key = cfg[name]
if not allowed[key] or used[key] then
error(
"Choose distinct control keys: letters, digits, F1 to F12, Space, Escape, CapsLock or Menu."
)
end
keys[name], used[key] = key, true
end
local held = {}
local stroke = nil
local invalidNotified = false
local function modified()
for _, key in
{ "LCtrl", "RCtrl", "LAlt", "RAlt", "LShift", "RShift", "LWin", "RWin" }
do
if Input.IsDown(key) then
return true
end
end
for n = 1, 5 do
if Input.IsDown("Mouse" .. n) then
return true
end
end
return false
end
local function finite(value, low, high)
return type(value) == "number"
and value == value
and value >= low
and value <= high
end
local function valid()
local ok = BANKS[cfg.bank] ~= nil
and finite(cfg.threshold, 5, 1000)
and finite(cfg.dominance, 1.1, 3)
if not ok then
stroke = nil
if not invalidNotified then
UI.Notify(
"Marking Gestures is inactive. Select a bank, set minimum stroke between 5 and 1000 counts and axis dominance between 1.1 and 3.",
"warning"
)
invalidNotified = true
end
else
invalidNotified = false
end
return ok
end
local function sameWindow()
local win = System.Window()
return stroke and win.process == stroke.process and win.title == stroke.title
end
local function finish()
if not stroke then
return
end
local current = stroke
local allowedToCommit = valid() and sameWindow() and not modified()
stroke = nil
if not allowedToCommit then
return
end
local x, y = math.abs(current.x), math.abs(current.y)
local direction
if x >= current.threshold and x >= y * current.dominance then
direction = if current.x < 0 then "left" else "right"
elseif y >= current.threshold and y >= x * current.dominance then
direction = if current.y < 0 then "up" else "down"
end
if direction then
HID.Combo(BANKS[current.bank][direction])
end
end
for _, name in { "layer", "cancel", "editing", "browsing", "media" } do
local key = keys[name]
Bind(key, {
when = function()
return held[key]
or (
if name == "layer" then valid() and not modified() else stroke ~= nil
)
end,
action = function()
if held[key] then
return nil
end
held[key] = true
if name == "layer" then
local win = System.Window()
stroke = {
x = 0,
y = 0,
bank = cfg.bank,
threshold = cfg.threshold,
dominance = cfg.dominance,
process = win.process,
title = win.title,
}
elseif name == "cancel" then
stroke = nil
else
stroke.bank = if name == "editing"
then "Editing"
elseif name == "browsing" then "Browsing"
else "Media"
UI.Notify(stroke.bank .. " gesture bank.", "info")
end
return nil
end,
release = function()
held[key] = nil
if name == "layer" then
finish()
end
end,
})
end
function OnMove(dx, dy)
if not stroke then
return true
end
if not valid() or modified() or not sameWindow() then
stroke = nil
return false
end
if not finite(dx, -1000000, 1000000) or not finite(dy, -1000000, 1000000) then
stroke = nil
return false
end
stroke.x += dx
stroke.y += dy
if math.abs(stroke.x) > 1000000 or math.abs(stroke.y) > 1000000 then
stroke = nil
end
return false
end
function OnTick()
for key in held do
if not Input.IsDown(key) then
held[key] = nil
if key == keys.layer then
stroke = nil
end
end
end
if stroke and (not valid() or modified() or not sameWindow()) then
stroke = nil
end
end
function OnBlur()
stroke = nil
end
function OnFocus()
stroke = nil
end
function OnStop()
stroke = nil
end
function OnError(message)
stroke = nil
Log.Error(message)
end