Hammerspoon



For anyone usingHammerspoon and folder ‘watchers’ to alert them to changes to a given folder, I’ve updated the watcher function I wrote about here

  1. Hammerspoon Download
  2. Hammerspoon Mac
  3. Hammerspoon Linux
  4. Hammerspoon Window Manager
  5. Hammerspoon
Hammerspoon send keys

The problem with the simple alert I demonstrated last time is that it only hangs around for a second or two (much less than a Folder Action alert, which takes a couple of minutes to time out). In this updated function, it now also writes a list of the file changes to a (by default) file on the Desktop. The write is an append: if the file doesn’t exist it will create it before writing; if it does exist, it will append the latest changes and date to the file. This way, even if you miss the alert, you’ll always have a record of what files have been added, deleted or modified in your watched folder.

Hammerspoon

Homebrew’s package index. This is a direct port of my Mjolnir Workflow. Installing Hammerspoon is much simpler without any external modules needing to be installed. Everything is internal.This workflow is for running scripts using Hammerspoon to control your windows. This is just a sample of the things that can be done. 24 Alternatives to Hammerspoon. Move your mouse over the green zoom button in any window, and Moom’s mouse control overlay will appear (as seen in the.

In this example, the folder being watched is ~/Library/LaunchAgents since we want to be aware of any adware, malware or other unsavoury processes being surreptitiously added by, for example, apps we download from the internet. Although there are, of course, many legitimate reasons for apps placing items in here, this folder is prime real estate for attackers as it is one of the locations that can launch processes at log in time without the user’s interaction (or knowledge).

Here’s the code, also available from my pastebin here. A code walkthrough follows.

Hammerspoon github

function writeStringToFileWithMode(aString, aPath, aMode)
local this_file
this_file = io.open(aPath, aMode)
this_file:write(aString)
this_file:close()
end
function myFolderWatch(files)
local str = 'Launch Agents folder was modified on ' .. os.date() .. ' :nt'
local this_path = os.getenv('HOME') .. '/Desktop/LaunchFolderModified.txt'
local ignore = 'DS_Store'
local count = 0
for _,file in pairs (files) do
count = count + 1
i = string.find(file, ignore)
if not i then
str = str .. file .. 'nt'
else
if count 1 then
str = 'n'
end
end
end
str = str .. 'n'
writeStringToFileWithMode(str, this_path, 'a')
if string.len(str) > 2 then
hs.alert.show('Launch Agents folder was modified.')
end
end
local aWatcher = hs.pathwatcher.new(os.getenv('HOME') .. '/Library/LaunchAgents/', myFolderWatch):start()


Code walkthrough
The first function, ‘writeStringToFileWithMode’ is just a convenience function. Hopefully the clue is in the name. The ‘aMode’ parameter is either “w” for write(over) or “a” for write(append).

The myFolderWatch function starts off by declaring some local variables.

‘str’ includes the initial line that we want to write to our output file and interpolates the time of the change by calling os.date().

Hammerspoon

‘this_path’ defines the path that we want to write our list of file names too.

The ‘ .. ‘ in both these assignments is a string concatenator (i.e., like ‘&’ in AppleScript or ‘stringByAppendingString’ in Obj-C).

‘ignore’ is a string that will help us to exclude .DS_Store files from triggering the alert or appearing in the list of file changes.

The ‘count’ variable is an internal var we need in order to eliminate .DS_Store changes when it’s the only change. Lua doesn’t have an easy way to count entries in tables, so we bascially iterate a variable each time through the loop to achieve the same effect.

After that, we have the ‘for’ loop. For loops in Lua are weird (at least for me), as you’ll see that they have this structure for a,b in pair (aPair). I won’t go into why, other than to say its a result of Lua’s table data structure. The '_' here is just a dummy variable for the first parameter. The ‘files’ in parentheses are the list of file names (not file specifiers, you AppleScripters!) that were added, deleted, or modified in the watched folder.

The loop begins by incrementing the count, then checks for the ignore file (.DS_Store). If the ignore file is not found, we then append the filename to the str variable.

If it is found, we check the count. If the count is ‘1’ (i.e., the only change was .DS_Store) we discard the entire str var and replace it with new line character. If the count is more than 1 we don’t do anything to ‘str’. We just ignore adding anything to it all.

At the end of the for loop we add another new line to the string just so our outputted text file looks nice and neat.

Then we call the write function mentioned above, passing ‘a’ (append) for the mode.

Finally, we fire the UI alert on the condition that the string has got more than 2 characters in it (if it didn’t it was just the “nn” string from ignoring the DS.Store file).

After the function definition, the aWatcher variable sets up the watcher on the path we want to observe and tells the watcher to start monitoring. It tells the watcher to call our myFolderWatch function when anything happens.

Deploying the code
After editing the config file, remember there’s two steps: i. save the file and ii. choose ‘Reload Config’ from the Hammerspoon menu.


Further Reading:
More details are available about Hammerspoon from the official site here.

My beginners guide with the original simple watcher function is here.

For anyone usingHammerspoon and folder ‘watchers’ to alert them to changes to a given folder, I’ve updated the watcher function I wrote about here

Hammerspoon Download

The problem with the simple alert I demonstrated last time is that it only hangs around for a second or two (much less than a Folder Action alert, which takes a couple of minutes to time out). In this updated function, it now also writes a list of the file changes to a (by default) file on the Desktop. The write is an append: if the file doesn’t exist it will create it before writing; if it does exist, it will append the latest changes and date to the file. This way, even if you miss the alert, you’ll always have a record of what files have been added, deleted or modified in your watched folder.

In this example, the folder being watched is ~/Library/LaunchAgents since we want to be aware of any adware, malware or other unsavoury processes being surreptitiously added by, for example, apps we download from the internet. Although there are, of course, many legitimate reasons for apps placing items in here, this folder is prime real estate for attackers as it is one of the locations that can launch processes at log in time without the user’s interaction (or knowledge).

Here’s the code, also available from my pastebin here. A code walkthrough follows.

function writeStringToFileWithMode(aString, aPath, aMode)
local this_file
this_file = io.open(aPath, aMode)
this_file:write(aString)
this_file:close()
end
function myFolderWatch(files)
local str = 'Launch Agents folder was modified on ' .. os.date() .. ' :nt'
local this_path = os.getenv('HOME') .. '/Desktop/LaunchFolderModified.txt'
local ignore = 'DS_Store'
local count = 0
for _,file in pairs (files) do
count = count + 1
i = string.find(file, ignore)
if not i then
str = str .. file .. 'nt'
else
if count 1 then
str = 'n'
end
end
end
str = str .. 'n'
writeStringToFileWithMode(str, this_path, 'a')
if string.len(str) > 2 then
hs.alert.show('Launch Agents folder was modified.')
end
end
local aWatcher = hs.pathwatcher.new(os.getenv('HOME') .. '/Library/LaunchAgents/', myFolderWatch):start()


Code walkthrough
The first function, ‘writeStringToFileWithMode’ is just a convenience function. Hopefully the clue is in the name. The ‘aMode’ parameter is either “w” for write(over) or “a” for write(append).

The myFolderWatch function starts off by declaring some local variables.

Hammerspoon Mac

‘str’ includes the initial line that we want to write to our output file and interpolates the time of the change by calling os.date().

‘this_path’ defines the path that we want to write our list of file names too.

The ‘ .. ‘ in both these assignments is a string concatenator (i.e., like ‘&’ in AppleScript or ‘stringByAppendingString’ in Obj-C).

‘ignore’ is a string that will help us to exclude .DS_Store files from triggering the alert or appearing in the list of file changes.

The ‘count’ variable is an internal var we need in order to eliminate .DS_Store changes when it’s the only change. Lua doesn’t have an easy way to count entries in tables, so we bascially iterate a variable each time through the loop to achieve the same effect.

After that, we have the ‘for’ loop. For loops in Lua are weird (at least for me), as you’ll see that they have this structure for a,b in pair (aPair). I won’t go into why, other than to say its a result of Lua’s table data structure. The '_' here is just a dummy variable for the first parameter. The ‘files’ in parentheses are the list of file names (not file specifiers, you AppleScripters!) that were added, deleted, or modified in the watched folder.

The loop begins by incrementing the count, then checks for the ignore file (.DS_Store). If the ignore file is not found, we then append the filename to the str variable.

Hammerspoon Linux

If it is found, we check the count. If the count is ‘1’ (i.e., the only change was .DS_Store) we discard the entire str var and replace it with new line character. If the count is more than 1 we don’t do anything to ‘str’. We just ignore adding anything to it all.

At the end of the for loop we add another new line to the string just so our outputted text file looks nice and neat.

Then we call the write function mentioned above, passing ‘a’ (append) for the mode.

Finally, we fire the UI alert on the condition that the string has got more than 2 characters in it (if it didn’t it was just the “nn” string from ignoring the DS.Store file).

After the function definition, the aWatcher variable sets up the watcher on the path we want to observe and tells the watcher to start monitoring. It tells the watcher to call our myFolderWatch function when anything happens.

Hammerspoon Window Manager

Deploying the code
After editing the config file, remember there’s two steps: i. save the file and ii. choose ‘Reload Config’ from the Hammerspoon menu.


Further Reading:
More details are available about Hammerspoon from the official site here.

Hammerspoon

My beginners guide with the original simple watcher function is here.