#!/bin/bash #Refresher - send F5 to a selected window on file change events #See: https://stackoverflow.com/questions/1346716/how-do-i-make-firefox-auto-refresh-on-file-change #Modified by Richard Neill; please consider this script in the public domain. #Be in the project-root before running this script. Then select the Firefox window when prompted. #Firefox re-sends xdotool's F5 to the firefox window/tab that FIREFOX last had focus on, NOT necessarily the Firefox window with the right ID. #This can play badly with focus-under-mouse. #Note: if using PHP, make sure that the page isn't being cached (opcache.revalidate_freq=0). #How useful is this? It probably saves: hand-to-mouse, move-mouse, Ctrl-(shift)-R, move mouse, hand back to kbd, i.e. 2 seconds, perhaps 100 times per hour. See: https://xkcd.com/1205/ #Monitor current directory. PROJECT_ROOT=$(pwd) #Exclude common backup patterns and those used for "unsaved" files: EXCLUDED=".*\.(swp|log|kate-swp|.*~)|.git/" #Don't retrigger more often than this; so that we don't get a cascade of refreshes. #Typically caused by editing multiple files at once, via "Make". DEAD_TIME=700000000 #0.7 second, in ns. #Normal reload command NORMAL_RELOAD="ctrl+r" #Full reload command (if it's JS/CSS or an image, and might be cached) FULL_RELOAD="ctrl+shift+r" #Key delay (ms, between keystrokes). Increase if the system doesn't respond. DELAY=25 if [ "$1" == "-h" -o "$1" == "--help" ];then echo "`basename $0`: reload a browser window in response to a change of any file in this directory". echo "1. Be in the root directory of the project before running this command." echo "2. Click the Firefox (or other browser) window to say which window should receive the Ctrl+R." echo "3. Each time a file is saved, the browser-window will reload." echo "" echo "It also sends a [Return] to make any form re-submit." echo "If it's a JS/CSS file, the window will do a full-reload (i.e. Ctrl+Shift+R)" echo "" echo "Ctrl-C to exit." echo "" echo "Note: the 'Ctrl-R' is sent to the Firefox window, not a specific tab." echo "Note: If you do file-save with Ctrl-S, remember to release the Ctrl-key after. Otherwise, you" echo " may get spurious 's' characters appearing in your editor. " echo "" exit 0 fi if ! which xdotool >/dev/null ; then echo "Please install: xdotool." >&2 exit 1 fi # use the first argument to search for a window name or let user select: if [ -z "$1" ]; then echo "Waiting for the user to select browser window..." wid=$(xdotool selectwindow) else echo "Searching for window with name '$1'..." wid=$(xdotool search --name "$1" | tail -n1) fi if [ -z $wid ]; then echo "window ID not found!" exit 1 fi echo "Using window id [$wid] for xdotool." #Trap Ctrl-C elegantly. Don't quit in the middle of an xdotool, lest we leave the mouse grabbed. trap ctrl_c INT function ctrl_c() { echo "Exiting..." setxkbmap -option grab:break_actions xdotool key XF86Ungrab exit 1 } #Inotify: only monitor close_write events. Otherwise the reload triggers apache to read the file ... which triggers inotify ... and it's circular! #Use -m so that we don't need to re-setup the inotifywait each time. timestamp=$(date +%s%N) made=""; inotifywait --recursive --exclude "$EXCLUDED" -e close_write -m $PROJECT_ROOT | while read line; do new_timestamp=$(date +%s%N) #nanoseconds past epoch. if [[ $((new_timestamp - timestamp)) -lt $DEAD_TIME ]]; then #skip if we are refreshing too often reload=skip elif [[ "$line" =~ (.scss)$ && -f Makefile ]]; then #if it's a .scss file, then re-run make first. reload=make elif [[ "$made" == true ]]; then #this is the loop after a "make". reload=$FULL_RELOAD made="" elif [[ "$line" =~ (.js|.css|.jpg|.png|.svg|.webp)$ ]]; then #full reload of page, if it's JS/CSS, or an image. reload=$FULL_RELOAD else reload=$NORMAL_RELOAD fi echo "changed: $line. TS=$new_timestamp; reload=$reload" >&2 if [[ $reload == "skip" ]]; then #Don't immediately re-trigger; just consume an event. (and don't update the timestamp). continue; elif [[ $reload == "make" ]]; then #Make - to update the scss. Then we'll re-trigger on the next cycle. make; made=true; continue; fi #Save the current timestamp, to prevent subsequent too-soon retrigger. timestamp=$(date +%s%N) #Save/restore the windowfocus; use Ctrl+R rather than F5; if reloading the page, send a Return to submit the dialog box (if any). current_window=$(xdotool getwindowfocus) xdotool windowfocus --sync $wid xdotool key --window $wid --delay $DELAY $reload Return xdotool windowfocus --sync $current_window done exit 0; #Various ways to do this, saved here just for reference. #1. Simple way. # xdotool key --window $wid F5 #2. But Make sure Firefox sends the F5 where it should go! Otherwise, especially with "focus under mouse", the F5 goes to the most-recently-focused Firefox window, #not the Window with $wid. Unless we also have the sleep, a side-effect is that we get unwanted vertical scroll-to-top. # cur=$(xdotool getwindowfocus) # xdotool windowfocus --sync $wid # sleep 0.2 # xdotool key --window $wid F5 # xdotool windowfocus --sync $cur #3. Alternative way. Get the middle of the window, move mouse, and then restore. This way, hopefully we can avoid the vertical scroll-to-top. #But this way of invoking xdotool doesn't support 'restore'. #xdotool mousemove --sync --window $wid 100 200 click 1 key F5 mousemove restore #4. Solution: save/restore the windowfocus, but use Ctrl+R rather than F5. #current_window=$(xdotool getwindowfocus) #xdotool windowfocus --sync $wid #xdotool key --window $wid ctrl+r #5. If the page had a form post, we do(!) want to resubmit it. So send a Return to submit the dialog-box (if any) #If we wanted to not do this, send Escape instead, and ensure that $delay is long enough that it doesn't prevent a normal (non-form-destination) page from normally-loading! current_window=$(xdotool getwindowfocus) xdotool windowfocus --sync $wid xdotool key --window $wid --delay $delay $reload Return xdotool windowfocus --sync $current_window #6. If the user presses and holds Ctrl, the presses S several times, the 2nd and subsequent S end up in the editor buffer, not triggering a Ctrl-S. #The best workaround is not do do this (i.e. release Ctrl after you pressed Ctrl+S). The --clearmodifiers option to xdotool makes the problem worse: #it leaves the Ctrl on afterwards, but gets the editor out of step.