Getting URLs from Safari

This post by David Sparks from a couple of days ago is quintessential David. It provides a useful little utility, is easy to understand, and includes a video that shows why you’d want to use it.

The utility is a Keyboard Maestro macro that gets the URL of the active tab in Safari, puts it on the clipboard, and then pastes it into whatever text you happen to be working on. I’ve been using a utility similar to it for almost a decade, and I can’t tell you how much time it’s saved me. You may think it’s no big deal to do “by hand” what this macro automates, and if you’re the kind of person who almost never sends links via Twitter or Facebook or texting, you might be right. But if you do much web communication, you’ll want to use David’s macro. Or something similar to it.

As it happens, the utility I currently use for this is also a Keyboard Maestro macro, but it has a long and convoluted history. It started out as a Python script (for absolutely no good reason, as most of what it did was run AppleScript) that was triggered by Quicksilver. Remember Quicksilver? Those were the days… Then I converted it to a TypeIt4Me snippet that ran a short AppleScript. Then I switched it to a TextExpander snippet that ran basically that same AppleScript. Finally, it became the Keyboard Maestro macro I use today:

Front URL macro

Throughout this journey, the key feature has always been this single line of AppleScript:

tell application "Safari" to get URL of front document

If you’re a Chrome user, you can do the same thing with this slightly longer line:1

tell application "Google Chrome" to tell front window to get URL of tab (active tab index)

Update 04/2/2017 9:16 PM
Rob Mathers reminds me of something I always forget: Keyboard Maestro’s Text Tokens.

@drdrang %SafariURL% and %ChromeURL% can be helpful (but KM-specific)…

Rob Mathers (@robmathers) Apr 2 2017 9:04 PM

Why don’t I remember these exist? I think it’s because they’re sort of like variables, and I tend to look for them in the Variables popup list. But thinking this way is exactly backward. In Keyboard Maestro, tokens are a fundamental feature, and variables are user-defined items that use a special form of the token syntax.

Maybe I’ll remember that now.

Probably not.

As you can see, the macro is triggered by typing ;furl. This snippet-based approach is a holdover from when it was run via TypeIt4Me and TextExpander. Although I could’ve changed it to a hotkey combination when I brought it over to Keyboard Maestro, I decided I preferred a typed string trigger. After all, the most common use is for the URL to be put into a text field where I’m typing.

Although my macro has only one action and David’s has five, his is still easier to understand because it requires no programming language syntax, which tends to scare people off. Anyone who’s ever toggled back and forth between Messages and Safari to send a link to a friend can see what David’s macro is doing. Mine, however, does have two small advantages that will keep me using it instead of switching to David’s:

  1. Mine doesn’t actually activate Safari, so the window I’m typing in always stays in front. David’s macro momentarily brings Safari to the front, which can be disconcerting for someone like me who tends to have messy, overlapping windows. You may not notice the momentary activation of Safari in David’s video because his windows are neatly arranged with no overlap.
  2. Mine doesn’t change the clipboard. Whatever I had on the clipboard before running the macro is still there afterward. Because David’s macro uses the clipboard to transport the URL out of Safari, whatever was on it before running the macro is gone. This probably isn’t a big deal for David’s audience, as I imagine most of them—including me—use a clipboard manager to hold onto a series of older clipboard contents. Still, I prefer to keep my clipboard history as clean as my window layout is messy.

In addition to this macro that pulls out the URL of the frontmost tab, I also have a series of macros that get the URLs of particular tabs. Here’s the one for the third tab (they’re numbered from left to right):

Third tab macro

I have six of these macros, all of which are triggered by typing something like ;3url. They were easy to write, but I’ve found over the years that they just aren’t as easy to use as I expected them to be. They’re useful only if I can see the Safari tab bar while I’m working in the other application, and if I have lots of tabs open, it’s hard to know which number I want. My workaround has been to ⌘-click on the tabs in the background Safari window. This changes the active tab without bringing Safari to the front and allows me to use the ;furl macro almost exclusively.

As I thought about writing this post, the difficulty of using the numbered tab macros began to bother me. There really should be a better way to get the URL of any open tab. So I built a new macro that puts up a window with a list of all the tabs in the front Safari window,2 from which I can select the tab whose URL I want.

Tab selection window

The window shows the names of the tabs, but the macro returns the URLs. The active tab is selected by default when the window appears. I use ;surl as the typed trigger to run the macro, which is called Some URL:

Some URL macro

Like the other macros, it has just one action, an AppleScript. Here’s the AppleScript:

 1:  tell application "Safari"
 2:    -- Initialize
 3:    set tabNames to {}
 4:    set tabURLs to {}
 5:    set frontName to name of front document
 7:    -- Collect the tab names and URLs from the top Safari window
 8:    set topWindow to window 1
 9:    set topTabs to every tab of topWindow
10:    repeat with t in topTabs
11:      set end of tabNames to name of t
12:      set end of tabURLs to URL of t
13:    end repeat
14:  end tell
16:  -- Display a list of names for the user to choose from
17:  tell application "System Events"
18:    set activeApp to name of first application process whose frontmost is true
19:    activate
20:    choose from list tabNames with title "Safari Tabs" default items frontName
21:    if result is not false then
22:      set nameChoice to item 1 of result
23:    else
24:      return
25:    end if
26:  end tell
28:  -- Return the URL of the selected tab
29:  tell application activeApp to activate
30:  repeat with t from 1 to the count of tabNames
31:    if item t of tabNames is nameChoice then return item t of tabURLs
32:  end repeat

Lines 1–14 collect from Safari the names and URLs of all the tabs in the front window, putting them into two parallel lists, tabNames and tabURLs.3 Line 5 gets the name of the active tab and puts it into frontName.

Line 18 gets the name of the active application, the one I’m typing in when I want to insert a URL. We’ll need this later. Line 19 activates System Events, a trick to ensure that the window with the list of tabs will be active. Without this line, the window created in Line 20 will appear in front of all the others, but won’t be active until you click in it, which is both weird and frustrating.

Line 20 puts up the window with the list of choices. If the user cancels, the result is false; otherwise, the result contains the selected tab name. Line 21 tests for this, either putting the tab name into nameChoice (Line 22) or exiting the script (Line 24).

Finally, Line 29 reactivates the application that was active when the macro was invoked (that’s what we saved in Line 18), and Lines 30–32 pull out the URL that corresponds to the chosen tab name. This is what gets inserted to replace ;surl.

So I’ve gone from a one-line script that might put people off to a 32-line script that definitely will. This is why David is far more popular.

  1. There may be a shorter way to get the URL from Chrome. I haven’t looked into its AppleScript library in years. 

  2. Which for me is almost always the only Safari window. 

  3. AppleScript has a record type, which is like a Python dictionary or a Perl hash. I thought about using a record to store the names and URLs, with the names as the keys and the URLs as the values. Unfortunately, AppleScript records aren’t nearly as useful as dictionaries and hashes. Parallel lists were just easier.