/
keystroke-animator
executable file
·198 lines (156 loc) · 4.37 KB
/
keystroke-animator
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
#!/usr/bin/env bash
set -e
# Join an array as delimited string.
# http://stackoverflow.com/a/17841619
function join { local IFS="$1"; shift; echo -E "$*"; }
function translateOnMac {
local char="$1"
shift
# List key codes:
# http://apple.stackexchange.com/questions/36943/how-do-i-automate-a-key-press-in-applescript
# http://apple.stackexchange.com/a/36947
# grep '^ *kVK' /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h|tr -d ,|while read x y z;do printf '%d %s %s\n' $z $z ${x#kVK_};done|sort -n
if [[ "$char" == "\"" ]];
then # 39 0x27 ANSI_Quote
echo "key code 39 using {shift down}"
elif [[ "$char" == $'\n' ]];
then # 36 0x24 Return
echo "key code 36"
else
echo "keystroke \"${char}\""
fi
}
function keystrokeOnMac {
local char="$1"
shift
local translated=$(translateOnMac "$char")
echo "tell application \"System Events\" to ${translated}" | osascript
}
function keystroke {
local char="$1"
shift
# TODO: other platforms?
keystrokeOnMac "$char"
}
function display {
echo -nE "$char"
}
function animator {
local animationOutputFilename="output.gif"
local -a filesAndSettings
while IFS= read -r -d '' file;
do
delay=$(echo "$file" | sed 's/.*_t\([[:digit:]][[:digit:]]*\).*/\1/g')
# 100 imagemagick ticks per seconds
filesAndSettings+=("-delay")
filesAndSettings+=("${delay}x100")
filesAndSettings+=("${file}")
filesAndSettings+=("-alpha")
filesAndSettings+=("remove")
done
convert -background "white" -alpha remove "${filesAndSettings[@]}" -layers "optimize" -loop 0 "$animationOutputFilename"
}
read -d '' getActiveWindowIdOnMacScript <<-'EOF' || true
tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell
tell app frontApp to id of front window
EOF
function getActiveWindowIdOnMac {
echo "$getActiveWindowIdOnMacScript" | osascript
}
function getActiveWindowId {
# TODO: other platforms?
getActiveWindowIdOnMac
}
function screenshotOnMac {
local filename="${1}"
shift
local activeWindowId=$(getActiveWindowId)
if [[ "$config_withShadow" == "false" ]];
then
screencapture -o "-l${activeWindowId}" "$filename"
else
screencapture "-l${activeWindowId}" "$filename"
fi
}
function screenshot {
local index=$(printf "%05d" "$1")
shift
local seconds="${1:-0.05}"
shift
# 100 imagemagick ticks per seconds
local -i ticks="$(echo "${seconds} * 100" | bc | sed 's/\..*$//g')"
local filename="keystroke-animator_i${index}_t${ticks}.png"
screenshotOnMac "$filename"
echo "${filename}"
}
function typeKeysFromStdin {
local -r keySleep="${1:-0.05}"
shift
local -r lineSleep="${1:-1.0}"
shift
local -r endSleep="${1:-5.0}"
shift
local screenshotList="screenshot-filelist.txt"
local -i screenshotIndex=0
local sleepSeconds
local -a screenshotFiles
[[ -s "$screenshotList" ]] && rm "$screenshotList"
touch "$screenshotList"
# Screenshot the initial state.
screenshot $(( screenshotIndex++ )) "$lineSleep" >>"$screenshotList"
while IFS= read -d '' -n 1 char; do
if [[ "$char" == $'\n' ]];
then
sleepSeconds="$lineSleep"
# Screenshot before and after return key.
# This allows typed terminal commands to be perceived before they flash by.
screenshot $(( screenshotIndex++ )) "$sleepSeconds" >>"$screenshotList"
else
sleepSeconds="$keySleep"
fi
display "$char"
keystroke "$char"
sleep "$sleepSeconds"
screenshot $(( screenshotIndex++ )) "$sleepSeconds" >>"$screenshotList"
done
# Screenshot final state, and hold for a while.
screenshot $(( screenshotIndex++ )) "$endSleep" >>"$screenshotList"
cat "$screenshotList" | tr '\n' '\0' | animator
}
function keystroker {
if [[ "$1" == "--no-shadow" ]];
then
config_withShadow="false"
shift
fi
local -ri countdown="${1:-5}"
shift || true
local -r keySleep="${1:-0.05}"
shift || true
local -r lineSleep="${1:-1.0}"
shift || true
local -r endSleep="${1:-5.0}"
shift || true
echo "keystroker: Now focus the window you want the typing to go in!"
for i in $(seq "$(( countdown - 1 ))" 0);
do
echo "keystroker: ${i}..."
sleep 1
done
echo "keystroker: Keystrokes are echoed in this window:"
typeKeysFromStdin "$keySleep" "$lineSleep" "$endSleep"
echo "keystroker: End of input."
}
function main {
if [[ "$1" == "--animator" ]];
then
shift
animator "$@"
else
keystroker "$@"
fi
}
declare config_withShadow="true"
main "$@"