forked from Botspot/automaton
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi
More file actions
executable file
·616 lines (553 loc) · 24.8 KB
/
api
File metadata and controls
executable file
·616 lines (553 loc) · 24.8 KB
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
#!/bin/bash
#export all functions and variables so they are reachable by bash subprocesses, such as in timeout and yad
set -a
#DO NOT use set -e. It breaks any function that needs to run a command (like yad) and check its error code with $?
#Make trap payload that exits out of parent function that ran error
set -o errtrace # Ensure ERR trap is inherited in functions
# Set ERR trap to exit from only the parent function that ran error
trap 'if [ "$exiting_parent_function_from_error" == 1 ] && [ ${#FUNCNAME[@]} -gt 1 ] && [ "${FUNCNAME[1]}" != "source" ]; then echo "hello from trap, exiting ${FUNCNAME[0]}()" >/dev/null; exiting_parent_function_from_error=0; return 1; fi' ERR
#correct behavior of error: break out of only its parent function, not all layers, not the script, just make the function that ran it return 1.
#or if error is run in the script, exit the script.
error() { #echo an error message in red text, and make the parent process (script or function) exit 1. Place "|| error '<explanation>'" at the end of ALL commands where success is necessary for proper function.
echo -e "\e[91m$1\e[39m" 1>&2
#if error is being run within another function, and we're not inside of api being sourced, and the trap payload that makes the parent function exit, then return 1
#situation where the trap payload is unavailable: separate shells run by yad. Better to exit 1 than have error do nothing. (and sourcing api again would be overkill)
if [ ${#FUNCNAME[@]} -gt 1 ] && [ "${FUNCNAME[1]}" != "source" ] && [[ "$(trap -p ERR)" == *exiting_parent_function_from_error* ]];then
#set this variable that enables the trap trigger to do anything
exiting_parent_function_from_error=1
return 1
else
exit 1
fi
}
warning() { #yellow text
echo -e "\e[93m\e[5m◢◣\e[25m WARNING: $1\e[0m" 1>&2
}
status() { #cyan text to indicate what is happening
#detect if a flag was passed, and if so, pass it on to the echo command
if [[ "$1" == '-'* ]] && [ ! -z "$2" ];then
echo -e $1 "\e[96m$2\e[0m" 1>&2
else
echo -e "\e[96m$1\e[0m" 1>&2
fi
}
status_green() { #announce the success of a major action
echo -e "\e[92m$1\e[0m" 1>&2
}
wait_for_file_creation() { #wait for the $1 file to be created, without using a loop
if [ -z "$1" ];then
error "wait_for_file_creation() requires a file argument"
elif [ -e "$1" ];then
return 0 #file already exists, exit immediately
fi
inotifywait -e create "$(dirname "$1")" --include "$(basename "$1")" -q >/dev/null
}
screenshot_fullscreen() { #take a screenshot of the full screen, save it to $1 png file
if [[ "$1" != '/'* ]];then
error "screenshot_fullscreen(): first argument must be a PNG filepath"
fi
if [ "$XDG_SESSION_TYPE" == x11 ];then
scrot -zoF "$1" || error "screenshot_fullscreen(): failed, please review errors above"
elif [ "$XDG_SESSION_TYPE" == wayland ];then
(sleep 2; warning "screenshot_fullscreen(): this is taking longer than expected. Most likely your wayland subscreen is not visible to your real screen, making it pause rendering. Please make the window visible, or switch to a headless subscreen.") &
local warning_pid=$!
grim "$1" || error "screenshot_fullscreen(): failed, please review errors above"
kill $warning_pid 2>/dev/null || status_green "screenshot_fullscreen(): Screenshot succeeded. Proceeding..."
else
error "screenshot_fullscreen(): XDG_SESSION_TYPE needs to be set to either 'x11' or 'wayland'"
fi
if [ ! -f "$1" ];then
error "screenshot_fullscreen(): screenshot tool somehow succeeded, but the screenshot was not saved to '$1'!"
fi
}
crop_image() { #given a $1 png image, crop it to $2 rectangle coordinates (X,Y +W+H), and save to $3
local inputfile="$1"
local cropregion="$2"
local outputfile="$3"
if [ -z "$inputfile" ] || [ -z "$cropregion" ] || [ -z "$outputfile" ];then
error "crop_image(): arguments required. [path to png image] [rectangle coordinates in the format 'X,Y +W+H']"
elif [ ! -f "$inputfile" ];then
error "crop_image(): input file '$inputfile' does not exist"
fi
#handle cases where we overwrite the original file
local convert_destination="$outputfile"
if [ "$inputfile" == "$outputfile" ];then
convert_destination="$RAM_LOCATION/automaton-${inputfile//\//-}.png"
fi
#crop the image, using imagemagick's preferred crop format syntax
convert "$inputfile" -crop "$(echo "$cropregion" | tr -d ' ' | tr ',+' ' ' | awk '{print $3"x"$4"+"$1"+"$2}')" +repage "$convert_destination" || error "Failed to crop image '$inputfile', please review errors above"
#handle cases where we overwrite the original file - now overwrite it
if [ "$inputfile" == "$outputfile" ];then
mv -f "$convert_destination" "$outputfile" || error "Failed to overwrite '$outputfile'"
fi
}
locator_start() { #start python opencv process that waits for targets on screen to find. Takes no arguments.
if [ -z "$locator_pipe" ];then
error 'locator_start(): $locator_pipe is not set! This should have been set when sourcing the automaton api.'
fi
rm -f "$locator_pipe" "${locator_pipe}.out" || error "locator_start(): Failed to first remove '$locator_pipe' file!"
#run it as a background process (it will stop when the named pipe is deleted)
"$AUTOMATON_DIR/locator.py" "$locator_pipe" &
#wait for the python script to create the named pipe using inotifywait
timeout 10 bash -c "wait_for_file_creation '$locator_pipe.out'" || error "locator.py failed to initialize within 10 seconds. Please refer to errors above."
}
locator_stop() { #stops python opencv process that waits for targets on screen to find. Takes no arguments.
rm -f "$locator_pipe" || error "locator_stop(): Failed to remove '$locator_pipe'!"
rm -f "${locator_pipe}.out" || error "locator_stop(): Failed to remove '${locator_pipe}.out'!"
}
locate_pattern() { #locate where on the screen the $1 pattern is, using opencv. $2 is optional offset argument in the form of +x+y, or a special offset 'center' which finds the midpoint of the pattern. Outputs X,Y. Exits with a stderr error message and exitcode 1, and no stdout output, if pattern not found. (set locate_pattern_stop_complaining=1 to hide the error)
#pattern filename to look for (assumed to be saved in project folder)
local pattern="$PATTERNS_DIR/$1"
if [ ! -f "$pattern" ];then
error "locate_pattern(): pattern png file at '$pattern' not found!"
elif [ ! -p "$locator_pipe" ];then
error "locate_pattern(): locator_start must be run first! ($locator_pipe does not exist)"
fi
#wayland screenshots on Pi OS at least keep the mouse in view for some reason, so move the mouse to the bottom right corner if running in a subscreen
if [ "$XDG_SESSION_TYPE" == wayland ] && [ "$original_DISPLAY" != "$DISPLAY" ];then
#moving the mouse in the main screen would compromise UX too much
wlrctl pointer move 1000000 1000000
fi
#pixel offset provided ahead of time. Format: +X+Y
if [[ "$2" == '+'* ]];then
local offset="$2"
elif [ "$2" == center ];then
#If requested, use center midpoint of pattern, which is +L/2+W/2 of the image dimensions.
local offset='' width='' height=''
local IFS=' '
read width height < <(identify -ping -format '%w %h' "$pattern")
# Calculate midpoint
offset="+$((width / 2))+$((height / 2))"
elif [ ! -z "$2" ];then
#offset not empty and unrecognized
error "locate_pattern(): invalid offset value: '$2'"
fi
#echo "offset is $offset" 1>&2
#save temporary screenshot to RAM - name it something unique for this script by using the full path of patterns directory, with '/' replaced with '-'
local screenshot="$RAM_LOCATION/automaton-${my_pid}.png"
screenshot_fullscreen "$screenshot" || return 1
echo -e "$pattern\t$screenshot" > "$locator_pipe" #this will block until locator reads it
#response is sent back through the pipe, this will wait for it
local location
local IFS=' '
if read location similarity <"${locator_pipe}.out" 2>/dev/null ;then #read blocks until something is in the output pipe
#read returns code 0 if [ ! -z "$location" ]
rm -f "$screenshot"
if [ ! -z "$offset" ];then
#if no offset given, output coordinates of top-left corner of pattern found
#otherwise, apply the offset before outputting it
local x_location="$(echo "$location" | awk -F, '{print $1}')"
local y_location="$(echo "$location" | awk -F, '{print $2}')"
local x_offset="$(echo "$offset" | awk -F + '{print $2}')"
local y_offset="$(echo "$offset" | awk -F + '{print $3}')"
location=$((x_location+x_offset)),$((y_location+y_offset))
fi
echo "$location $similarity"
status_green "'$1' found at $location - $similarity"
else
#read returns code 1 if it received nothing (pattern not found)
rm -f "$screenshot"
#message user that pattern could not be found, UNLESS this function is being run by locate_pattern_retry
local IFS=' '
if [ "$locate_pattern_stop_complaining" == 1 ];then
#avoid spamming, while allowing other error messages to go unhindered
return 1
else
#run in parenthesis to ensure it acts as return 1, not exit 1, even though it should be return 1 with the trap trick
(error "locate_pattern(): failed to find '$1' on screen.")
fi
fi
}
locate_pattern_retry() { #runs locate_pattern repeatedly until it succeeds. locate_pattern inherits all arguments, stdout/stderr is passed on.
echo "Waiting to locate pattern '$1'... " 1>&2
local locate_pattern_stop_complaining=1
while ! locate_pattern "$@" ;do
true
done
}
mouse_move() { #move mouse to absolute position $1 (format X,Y). Rather than specify raw coordinates, use something like $(locate_pattern button.png center). Also, if applicable, don't forget to mouse_click after moving the mouse!
local IFS=' ' #ensure the $() gets split into 2 arguments
local coordinates="$(echo "$1" | awk '{print $1}')"
if [ -z "$coordinates" ];then
error "mouse_move(): no input coordinates"
fi
if [ "$XDG_SESSION_TYPE" == x11 ];then
#xdotool allows absolute mouse movements
xdotool mousemove --sync $(echo "$coordinates" | tr ',' ' ') || error "mouse_move(): xdotool failed to move the mouse to '$coordinates'"
elif [ "$XDG_SESSION_TYPE" == wayland ];then
#wlrctl only allows relative movements, so move to top left corner first
wlrctl pointer move -1000000 -1000000
#and then move to desired location, replacing ',' with space
wlrctl pointer move $(echo "$coordinates" | tr ',' ' ') || error "mouse_move(): wlrctl failed to move the mouse to '$coordinates'"
fi
}
mouse_click() { #normal mouse click
if [ "$XDG_SESSION_TYPE" == x11 ];then
xdotool click 1
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl pointer click left
fi
}
mouse_right_click() { #mouse do a right-click
if [ "$XDG_SESSION_TYPE" == x11 ];then
xdotool click 3
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl pointer click left
fi
}
mouse_middle_click() { #mouse press the scroll wheel
if [ "$XDG_SESSION_TYPE" == x11 ];then
xdotool click 2
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl pointer click middle
fi
}
mouse_scroll() { #scroll $1=[up/down] $2 times (scroll once if $2 not specified)
local scrolldirection="$1"
local scrolltimes="$2"
if [ -z "$scrolltimes" ];then
scrolltimes=1 #default value 1 if not set
fi
if [ "$scrolldirection" == down ];then
#scroll down
if [ "$XDG_SESSION_TYPE" == x11 ];then
for ((n=0;n<$scrolltimes;n++)); do
xdotool click 5
done
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl pointer scroll $((15*scrolltimes))
fi
elif [ "$scrolldirection" == up ];then
#scroll up
if [ "$XDG_SESSION_TYPE" == x11 ];then
for ((n=0;n<$scrolltimes;n++)); do
xdotool click 4
done
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl pointer scroll $((-15*scrolltimes))
fi
else
error "mouse_scroll(): first argument must be 'up' or 'down'. Got '$1'"
fi
sleep 0.5s #browsers animate after scroll event, so wait for it to finish
}
clipboard_set() { #copy text in $1 to the primary clipboard
if [ "$XDG_SESSION_TYPE" == x11 ];then
echo -n "$1" | xclip -i -selection clipboard
elif [ "$XDG_SESSION_TYPE" == wayland ];then
echo -n "$1" | wl-copy
fi
}
clipboard_dump() { #output the contents of the primary clipboard to stdout
if [ "$XDG_SESSION_TYPE" == x11 ];then
xclip -o -selection clipboard
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wl-paste
fi
}
keyboard_type() { #type text in $1 with the keyboard
if [ "$XDG_SESSION_TYPE" == x11 ];then
echo -n "$1" | xdotool type --file -
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlrctl keyboard type "$1"
fi
}
keyboard_shortcut() { #press multiple keys at once, and special keys. one argument per key. Example: keyboard_shortcut Ctrl Alt a (VERY IMPORTANT: keyboard_shortcut accepts ONLY: Ctrl, Shift, Alt, Super, Enter, Tab, Escape, Backspace, Space, and ASCII characters. Anything not in that list, including F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12, will be treated as individual literal letters. For example 'keyboard_shortcut Alt F4' will not press Alt+F4, it will press and hold Alt while typing the letter F and the number 4. To close a window, use Ctrl w, Ctrl q, Escape, or click a close button.)
local wlrctl_modifiers=""
local wlrctl_letters=""
local x_key_combo=""
while [ $# -gt 0 ]; do
case "$1" in
Ctrl)
wlrctl_modifiers+=",CTRL"
x_key_combo+="+ctrl"
;;
Shift)
wlrctl_modifiers+=",SHIFT"
x_key_combo+="+shift"
;;
Alt)
wlrctl_modifiers+=",ALT"
x_key_combo+="+alt"
;;
Super)
wlrctl_modifiers+=",SUPER"
x_key_combo+="+super"
;;
Tab)
wlrctl_letters+=$'\t'
x_key_combo+="+Tab"
;;
Escape)
wlrctl_letters+=$'\e'
x_key_combo+="+Escape"
;;
Backspace)
wlrctl_letters+=$'\b'
x_key_combo+="+BackSpace"
;;
Enter)
wlrctl_letters+=$'\n'
x_key_combo+="+Return"
;;
Space)
wlrctl_letters+=' '
x_key_combo+="+space"
;;
*)
wlrctl_letters+="$1"
x_key_combo+="+${1}"
;;
esac
shift
done
if [ "$XDG_SESSION_TYPE" = x11 ]; then
if [ -z "$x_key_combo" ]; then
error "On X11, xdotool requires a key combination to use at least 1 key."
else
xdotool key "${x_key_combo#+}"
fi
elif [ "$XDG_SESSION_TYPE" = wayland ]; then
if [ -z "$wlrctl_letters" ]; then
error "On Wayland, wlrctl requires a key combination to use at least 1 key that is NOT one of the following keys: Ctrl, Shift, Alt, Super."
elif [ -n "$wlrctl_modifiers" ]; then
wlrctl type "$wlrctl_letters" modifiers "${wlrctl_modifiers#,}"
else
wlrctl type "$wlrctl_letters"
fi
fi
}
subscreen_start() { #start a nested x or wayland display (type must be specified in $1: x11, x11-headless, wayland, wayland-headless)
local display_type="$1"
local display_dimensions="$2"
local line #don't export last line processed by the while loops
#parse display dimensions, if given
local display_width
local display_height
if [ -z "$display_dimensions" ];then
display_dimensions=1366x768
fi
display_width="$(echo "$display_dimensions" | awk -Fx '{print $1}')"
display_height="$(echo "$display_dimensions" | awk -Fx '{print $2}')"
if [ -z "$display_width" ] || [ -z "$display_height" ];then
error "subscreen(): display dimensions not set correctly! Value was: '$display_dimensions'"
fi
case "$display_type" in
x11|x11-headless)
#find an unoccupied x display to use
x_display=0
while [ -e /tmp/.X11-unix/X$x_display ];do
x_display=$((x_display+1))
done
#different tools are used, depending on whether the user wants a headless or a visible window
case "$display_type" in
x11)
#xephyr
Xephyr -no-host-grab -title "Automaton nested x11 display on :$x_display" -ac -screen ${display_width}x${display_height} -br -glamor -dpi 96 ":$x_display" &
export subscreen_pid=$!
;;
x11-headless)
#xvfb
Xvfb -ac -screen ${display_width}x${display_height} -br -glamor -dpi 96 ":$x_display" 2>/dev/null
export subscreen_pid=$!
;;
esac
#before this function exits, the display needs to be ready for connections.
#Wait for lockfile creation
wait_for_file_creation /tmp/.X11-unix/X$x_display
#all apps downstream now use this DISPLAY and XDG_SESSION_TYPE, and ensure WAYLAND_DISPLAY is unset
export XDG_SESSION_TYPE=x11
export DISPLAY=:$x_display
unset WAYLAND_DISPLAY
export WAYLAND_DISPLAY
#a desktop manager must be run, otherwise nothing can be resized or maximized
local obconf='<?xml version="1.0" encoding="UTF-8"?><openbox_config><theme><name>Onyx</name></theme></openbox_config>'
while read line ;do
#run openbox in the background, but do not proceed until it has initialized
if [ "$line" == 'openbox ready' ];then
break
fi
#give openbox a minimal config so that it does not complain, and its output must be piped through cat, otherwise it instantly exits for some reason.
done < <(echo "$obconf" | openbox --sm-disable --config-file /dev/stdin --startup 'echo openbox ready' | cat)
;;
wayland|wayland-headless)
#wayland nested display
#determine which backend to use (the environment in which sway runs)
local wlr_backend
if [ "$display_type" == wayland-headless ];then
wlr_backend="headless"
elif [ "$XDG_SESSION_TYPE" == x11 ];then
wlr_backend="x11"
elif [ "$XDG_SESSION_TYPE" == wayland ];then
wlr_backend="wayland"
else
error "subscreen_start(): Seems like your system is headless. (no wayland or x11 desktop session found)\nTo run this function headless, use the 'wayland-headless' mode."
fi
#determine output name for config
local output_name
case "$wlr_backend" in
headless) output_name="HEADLESS-1" ;;
x11) output_name="X11-1" ;;
wayland) output_name="WAYLAND-1" ;;
esac
#create temporary sway config with resolution and startup echoes
if [ -z "$sway_config" ];then
error "start_subscreen(): sway_config should have been set when sourcing api"
fi
echo "output $output_name mode ${display_width}x${display_height}"'\
default_border none
exec echo "WAYLAND_DISPLAY=$WAYLAND_DISPLAY"
exec echo "DISPLAY=$DISPLAY"
exec echo "sway_ready"' >> "$sway_config"
#if there's a base config, you could include it: echo "include ${AUTOMATON_DIR}/sway.ini" at top
#run sway, capture output to get the env variables
local sway_succeeded=0
while read line ;do
if [[ "$line" == WAYLAND_DISPLAY=* ]] || [[ "$line" == DISPLAY=* ]];then
export "$line" #export the variable that sway informed us of
elif [[ "$line" == subscreen_pid=* ]];then
export "$line" #export subscreen_pid for subscreen_stop
elif [[ "$line" == sway_ready ]];then
#if sway got far enough to run the execs, it did not fail
sway_succeeded=1
break #we have reached the end of useful output, DISPLAY and WAYLAND_DISPLAY have already been set, so exit and leave sway running as a background process
fi
#use sway because it is the only wayland compositor that works in nested mode on pi os (rules out wayfire), while being available on ubuntu repos (rules out labwc), while being based on wlroots (rules out mutter)
done < <(WLR_BACKEND="$wlr_backend" WLR_RENDERER=pixman WLR_NO_HARDWARE_CURSORS=1 WLR_RENDERER_FORCE_SOFTWARE=1 sway -c "$sway_config" & subscreen_pid=$! ; echo "subscreen_pid=$subscreen_pid" ; wait $subscreen_pid)
rm "$sway_config"
if [ "$sway_succeeded" == 0 ];then
error "subscreen_start(): Sway failed to start! Please review errors above."
fi
export XDG_SESSION_TYPE=wayland
;;
*)
error "subscreen(): not sure if you need a X11 or Wayland display, headless or not.\nPlease use 1 of the following 4 arguments: x11, x11-headless, wayland, wayland-headless"
;;
esac
#from this point on, any GUI programs run by the userscript will spawn in the nested display.
#make sure the nested display is killed when the userscript ends
trap "subscreen_stop" EXIT SIGINT
}
subscreen_stop() { #stop a running subscreen. Takes no arguments.
kill $subscreen_pid 2>/dev/null || true
rm -f "$sway_config"
export DISPLAY="$original_DISPLAY"
export WAYLAND_DISPLAY="$original_WAYLAND_DISPLAY"
export XDG_SESSION_TYPE="$original_XDG_SESSION_TYPE"
}
#set variables
export AUTOMATON_DIR #necessary because this is usually assigned before sourcing api, which runs `set -a`
if [ -z "$PATTERNS_DIR" ] && [ -z "$AUTOMATON_DIR" ];then
error "api: PATTERNS_DIR and AUTOMATON_DIR must be set by the script that sources api"
elif [ ! -d "$PATTERNS_DIR" ] && [[ "$0" != */gui ]];then
error "api: PATTERNS_DIR does not seem to be set correctly. ('$PATTERNS_DIR' does not exist)"
elif [ ! -f "$AUTOMATON_DIR/api" ];then
error "api: AUTOMATON_DIR does not seem to be set correctly. ($AUTOMATON_DIR/api does not exist)"
fi
if [ -d /dev/shm ];then
RAM_LOCATION=/dev/shm
else
RAM_LOCATION=/tmp
fi
#other variables that we want global to modify later within functions:
subscreen_pid=''
my_pid=$$
#keep original values to these variables for subscreen_stop()
original_DISPLAY="$DISPLAY"
original_WAYLAND_DISPLAY="$WAYLAND_DISPLAY"
original_XDG_SESSION_TYPE="$XDG_SESSION_TYPE"
sway_config="$RAM_LOCATION/sway_config_${my_pid}.ini"
#install dependencies
install_list=()
#these are needed on both X11 or Wayland
if [ ! -d /usr/share/doc/python3-opencv ];then
install_list+=('python3-opencv')
fi
if [ ! -d /usr/share/doc/python3-numpy ];then
install_list+=('python3-numpy')
fi
if ! command -v inotifywait >/dev/null ;then
install_list+=('inotify-tools')
fi
if ! command -v identify >/dev/null ;then
install_list+=('imagemagick-6.q16')
fi
#below this line are dependencies used by the gui
if [ ! -d /usr/share/doc/python3-pygame-sdl2 ];then
install_list+=('python3-pygame-sdl2')
fi
if ! command -v yad >/dev/null ;then
install_list+=('yad')
fi
if ! command -v shellcheck >/dev/null ;then
install_list+=('shellcheck')
fi
#nested display tools: if you do not plan to use subscreen function, set SKIP_INSTALLING_SUBSCREEN_DEPS=1 to avoid installing these
if [ "$SKIP_INSTALLING_SUBSCREEN_DEPS" != 1 ];then
if ! command -v Xvfb >/dev/null ;then
install_list+=('xvfb')
fi
if ! command -v Xephyr >/dev/null ;then
install_list+=('xserver-xephyr')
fi
if ! command -v sway >/dev/null ;then
install_list+=('sway')
fi
if ! command -v openbox >/dev/null ;then
install_list+=('openbox')
fi
fi
#X11 or Wayland have different dependencies
if [ "$XDG_SESSION_TYPE" == x11 ];then #these dependencies are required only on distros that are X11
if ! command -v xdotool >/dev/null ;then
install_list+=('xdotool')
fi
if ! command -v scrot >/dev/null ;then
install_list+=('scrot')
fi
if ! command -v xclip >/dev/null ;then
install_list+=('xclip')
fi
elif [ "$XDG_SESSION_TYPE" == wayland ];then #these dependencies are required only on distros that are Wayland
#for compiling wlrctl
if ! command -v wlrctl >/dev/null ;then
need_to_compile_wlrctl=yes
install_list+=('cmake' 'libxkbcommon-dev' 'libwayland-dev')
fi
if ! command -v grim >/dev/null ;then
install_list+=('grim')
fi
if ! command -v wl-copy >/dev/null ;then
install_list+=('wl-clipboard')
fi
fi
#now if anything is missing, install it
if [ ! -z "${install_list[*]}" ];then
status "Installing dependencies... running sudo apt update"
sudo apt update || true
status "Installing dependencies: ${install_list[*]}"
sudo apt install -y "${install_list[@]}" || error "Failed to install dependencies! Please install them yourself: ${install_list[*]}"
status_green "Dependencies installed successfully."
fi
#compile and install wlrctl if on wayland
if [ "$need_to_compile_wlrctl" == yes ];then
status "Compiling wlrctl..."
#run in a subshell to avoid changing current directory (downloads and installs in ram)
(cd "$RAM_LOCATION"
rm -rf ./wlrctl
git clone https://git.sr.ht/~brocellous/wlrctl || exit 1
cd wlrctl
meson setup --prefix=/usr/local build || exit 1
sudo ninja -C build install || exit 1
cd "$RAM_LOCATION"
rm -rf ./wlrctl) || error "Failed to compile wlrctl. Please review errors above."
status_green "wlrctl has installed successfully."
fi
#now that dependencies are installed, do other things before userscript runs
#choose appropriate location for locator.py named pipe
locator_pipe="$PATTERNS_DIR/locator_pipe"
#remove it while being sourced
rm -f "$locator_pipe"
#stop locator.py when userscript ends so only advanced users need to run locator_stop
trap locator_stop EXIT SIGINT
status_green "Automaton API has been sourced"