--- ==== Seal.plugins.useractions ==== --- --- Allow accessing user-defined bookmarks and arbitrary actions from Seal. --- local obj = {} obj.__index = obj obj.__basename = "useractions" obj.__name = "seal_" .. obj.__basename obj.default_icon = hs.image.imageFromName(hs.image.systemImageNames.ActionTemplate) --- Seal.plugins.useractions.actions --- Variable --- --- Notes: --- * A table containing the definitions of static user-defined actions. Each entry is indexed by the name of the entry as it will be shown in the chooser. Its value is a table which can have the following keys (one of `fn` or `url` is required. If both are provided, `url` is ignored): --- * fn - A function which will be called when the entry is selected. The function receives no arguments. --- * url - A URL which will be opened when the entry is selected. Can also be non-HTTP URLs, such as `mailto:` or other app-specific URLs. --- * description - (optional) A string or `hs.styledtext` object that will be shown underneath the main text of the choice. --- * icon - (optional) An `hs.image` object that will be shown next to the entry in the chooser. If not provided, `Seal.plugins.useractions.default_icon` is used. For `url` bookmarks, it can be set to `"favicon"` to fetch and use the website's favicon. --- * keyword - (optional) A command by which this action will be invoked, effectively turning it into a Seal command. Any arguments passed to the command will be handled as follows: --- * For `fn` actions, passed as an argument to the function --- * For `url` actions, substituted into the URL, taking the place of any occurrences of `${query}`. --- * hotkey - (optional) A hotkey specification in the form `{ modifiers, key }` by which this action can be invoked. --- * Example configuration: --- ``` --- spoon.Seal:loadPlugins({"useractions"}) --- spoon.Seal.plugins.useractions.actions = --- { --- ["Hammerspoon docs webpage"] = { --- url = "http://hammerspoon.org/docs/", --- icon = hs.image.imageFromName(hs.image.systemImageNames.ApplicationIcon), --- description = "Open Hammerspoon documentation", --- hotkey = { hyper, "h" }, --- }, --- ["Leave corpnet"] = { --- fn = function() --- spoon.WiFiTransitions:processTransition('foo', 'corpnet01') --- end, --- }, --- ["Arrive in corpnet"] = { --- fn = function() --- spoon.WiFiTransitions:processTransition('corpnet01', 'foo') --- end, --- }, --- ["Translate using Leo"] = { --- url = "http://dict.leo.org/ende/index_de.html#/search=${query}", --- icon = 'favicon', --- keyword = "leo", --- }, --- ["Tell me something"] = { --- keyword = "tellme", --- fn = function(str) hs.alert.show(str) end, --- } --- ``` obj.actions = {} --- Seal.plugins.useractions.get_favicon --- Variable --- --- If `true`, attempt to obtain the favicon for URLs added through the `add` command, and use it in the chooser. Defaults to `true` obj.get_favicon = true -- Internal functions for storing/retrieving bookmarks in the settings database. local getSetting = function(label, default) return hs.settings.get(obj.__name.."."..label) or default end local setSetting = function(label, value) hs.settings.set(obj.__name.."."..label, value); return value end -- Internal variable where the dynamically-added bookmarks are kept obj.stored_actions = getSetting('stored_actions', {}) -- Internal variable where the merged list of bookmarks/actions is kept obj.all_actions = nil function update_all_actions() if (obj.all_actions == nil) then obj.all_actions = {} for k,v in pairs(obj.actions) do obj.all_actions[k] = hs.fnutils.copy(v) end for k,v in pairs(obj.stored_actions) do obj.all_actions[k] = hs.fnutils.copy(v) if v.encoded_icon then obj.all_actions[k].icon = hs.image.imageFromURL(v.encoded_icon) end end end end function obj:commands() local cmds={ add = { cmd = "add", fn = obj.choicesAddURLCommand, name = "Add URL", description = "Add URL to bookmarks", plugin = obj.__name }, del = { cmd = "del", fn = obj.choicesDelURLCommand, name = "Delete URL", description = "Delete URL from bookmarks", plugin = obj.__name } } local hotkeys_def = {} local hotkeys_map = {} local any_hotkeys = false for k,v in pairs(self.actions or {}) do if v.keyword and (not cmds[v.keyword]) then if v.url ~= nil and v.icon == 'favicon' then v.icon = obj.favIcon(v.url) end cmds[v.keyword] = { cmd = v.keyword, fn = hs.fnutils.partial(obj.choicesActionKeyword, k, v), name = k, icon = v.icon, plugin = obj.__name } end if v.hotkey then local choice = obj.buildChoice(k,v) hotkeys_def[k] = hs.fnutils.partial(obj.completionCallback, choice) hotkeys_map[k] = v.hotkey any_hotkeys = true end end if any_hotkeys then hs.spoons.bindHotkeysToSpec(hotkeys_def, hotkeys_map) end return cmds end function obj:bare() return self.bareActions end function obj.buildChoice(action, v) local icon, kind local choice=nil if type(v) == 'table' then if v.fn then kind = 'runFunction' elseif v.url then kind = 'openURL' if v.icon == 'favicon' then v.icon = obj.favIcon(v.url) end end icon = v.icon or obj.default_icon choice = {} choice.text = action choice.type = kind choice.plugin = obj.__name choice.image = icon if v.description then choice.subText = v.description end end return choice end function obj.bareActions(query) local choices = {} if query == nil or query == "" then return choices end update_all_actions() obj.seal:refreshCommandsForPlugin(obj.__basename) for action,v in pairs(obj.all_actions) do if string.match(action:lower(), query:lower()) then local choice = obj.buildChoice(action, v) if choice then table.insert(choices, choice) end end end return choices end function obj.favIcon(url) local query=string.format("http://www.google.com/s2/favicons?sz=64&domain_url=%s", hs.http.encodeForQuery(url)) return hs.image.imageFromURL(query) end function obj.choicesAddURLCommand(query) local choices = {} if query == ".*" then query = " " end local url,name = string.match(query, "([^%s]+)%s+(.*)") local subtext = "" if url then subtext = string.format("New bookmark '%s' pointing to %s", name,url) end local choice = { text = "add " .. query, subText = subtext, url = url, name = name, plugin = obj.__name, type = 'addURL', } table.insert(choices, choice) return choices end function obj.choicesDelURLCommand(query) local choices = {} for k,v in pairs(obj.stored_actions) do if string.match(k:lower(), query:lower()) or string.match(v.url:lower(), query:lower()) then local choice = { text = string.format("delete '%s'", k), subText = v.url, delKey = k, plugin = obj.__name, type = 'delURL', } if v.encoded_icon then choice.image = hs.image.imageFromURL(v.encoded_icon) end table.insert(choices, choice) end end return choices end function obj.choicesActionKeyword(action, def, query) local choices = {} if query == ".*" then query = "" end local choice = { text = def.keyword .. " " .. query, subText = def.description or action, actionname = action, arg = query, plugin = obj.__name, image = def.icon, type = 'invokeKeyword', } table.insert(choices, choice) return choices end function obj.openURL(url) hs.execute(string.format("/usr/bin/open '%s'", url)) end function obj.completionCallback(row) update_all_actions() if row.type == 'runFunction' then local fn = obj.all_actions[row.text].fn fn() elseif row.type == 'openURL' then local url = obj.all_actions[row.text].url obj.openURL(url) elseif row.type == 'addURL' then obj.stored_actions[row.name] = { url = row.url } obj.all_actions = nil if obj.get_favicon then local ico=obj.favIcon(row.url) if ico then obj.stored_actions[row.name]['encoded_icon'] = ico:encodeAsURLString() end end setSetting('stored_actions', obj.stored_actions) elseif row.type == 'delURL' then obj.stored_actions[row.delKey] = nil obj.all_actions = nil setSetting('stored_actions', obj.stored_actions) elseif row.type == 'invokeKeyword' then if obj.actions[row.actionname].fn then obj.actions[row.actionname].fn(row.arg) elseif obj.actions[row.actionname].url then row.arg = hs.http.encodeForQuery(row.arg) local query = row.arg:gsub("%%", "%%%%") local url = string.gsub(obj.actions[row.actionname].url, '${query}', query) obj.openURL(url) end end end return obj