One day, I installed some software with Homebrew — it was a month ago, so I can’t remember what. There were no immediate problems, except that two days later, when I rebooted my computer, Emacs, also installed by Homebrew, involuntarily failed to boot. I’m a heavy org-mode user, and occasionally need to write Common Lisp code in SLIME, which runs in Emacs.

My gut tells me that maybe a re-installation of Emacs will restore everything to normal. After reinstalling Emacs, I ran into another problem — the buttons added to the Touch Bar with BetterTouchTools couldn’t switch to Emacs Windows when it was already started.

To be sure, it’s not a big problem. After all, most of the time, the MacBook Pro is used as the host with the lid closed, and the Touch Bar is not used frequently when working. Also, you don’t need Emacs to paste code in languages like node.js — VSCode is more appropriate.

But it was annoying, so I decided to fix it — with Hammerspoon.

What is Hammerspoon?

Hammerspoon’s website has a good description of how the tool is positioned and how it works

This is a tool for powerful automation of OS X. At its core, Hammerspoon is just a bridge between the operating system and a Lua scripting engine. What gives Hammerspoon its power is a set of extensions that expose specific pieces of system functionality, to the user.

  1. It runs on OS X — macOS now;
  2. It’s designed to automate operations — just like it’s built into the systemAutomatorOr third partyAlfred WorkflowLike that;
  3. Its principle is to encapsulate the functions of the operating system into usableLuaThe module that the code calls;

For example, the following code

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "E".function(a)
  hs.alert.show("Hello World!")
end)
Copy the code

Allows the user to invoke Hello World to the center of the screen while pressing the ⌘⌥⌃e key. This text is

Why Hammerspoon?

Hammerspoon solves my problem with its hs.window module, which allows users to either traverse all open Windows (using the hs.window.allWindows function) or focus on a specific window (using the focus method). With them, it’s a natural progression to move Emacs to front-most:

  1. Call a functionhs.window.allWindowsFunction to get a list of all Windows;
  2. Check window objects in the list one by one if they belong toEmacsCall the window methodfocus“And out of the loop.

The remaining two questions are:

  1. Emacsthebundle IDWhat is;
  2. How do I know a window objectbundle ID.

Emacs的bundle ID

The Bundle ID uniquely identifies an application in macOS. Want to know what is the bundle ID of Emacs, only need to open the file/Applications/Emacs. The app/Contents/Info. The plist, look at the key CFBundleIdentifier value.

➜  Contents grep -A 1 'CFBundleIdentifier' Info.plist
	<key>CFBundleIdentifier</key>
	<string>org.gnu.Emacs</string>
➜  Contents
Copy the code

As you can see, the bundle ID of Emacs is org.gnu.

A little Lua code

Armed with the Bundle ID of Emacs, you can now define shortcuts in Hammerspoon. Since this set of shortcuts will eventually be triggered by buttons on the Touch Bar, it doesn’t matter if they are more complex, so I just use the ⌘⌥⌃ W in Hammerspoon’s starter guide as an example

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
end)
Copy the code

To iterate through window objects one by one in a loop, save the return value of hs.window.allWindows to a local variable

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
      local windows = hs.window.allWindows()
end)
Copy the code

Follow the example of this article in Jane’s book to iterate through the variable Windows using for and Pairs

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
      local windows = hs.window.allWindows()
      - in Lua traverses the table method: https://www.jianshu.com/p/de5a4b132918
      for _, win in pairs(windows) do
      end
end)
Copy the code

The window itself does not have a bundle ID, so you need to obtain the application to which the window belongs. As you can see from the documentation, there is an Application method that is used to get the application object

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
      local windows = hs.window.allWindows()
      - in Lua traverses the table method: https://www.jianshu.com/p/de5a4b132918
      for _, win in pairs(windows) do
         local app = win:application()
      end
end)
Copy the code

AllWindows is called with an English period (.) Application is called with a colon (:), which is the syntactic difference between calling functions and methods in Lua.

Use the application’s bundleID method to get its Bundle ID

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
      local windows = hs.window.allWindows()
      - in Lua traverses the table method: https://www.jianshu.com/p/de5a4b132918
      for _, win in pairs(windows) do
         local app = win:application()
         local bundleID = app:bundleID()
      end
end)
Copy the code

Now, as long as the variable bundleID equals the Bundle ID of Emacs, you can focus on the window being traversed

hs.hotkey.bind({"cmd"."alt"."ctrl"}, "W".function(a)
      local windows = hs.window.allWindows()
      - in Lua traverses the table method: https://www.jianshu.com/p/de5a4b132918
      for _, win in pairs(windows) do
         local app = win:application()
         local bundleID = app:bundleID()
         if bundleID == "org.gnu.Emacs" then
            win:focus()
         end
      end
end)
Copy the code

Let the Touch Bar button trigger it all

Just configure it in BetterTouchTools

This method is better than the previous method of invoking /Applications/ emacs.app because it relies only on the logical impermanence of Emacs — the bundle ID — and not on its physical installation location.

Read the original