Efficient Mac Window Management

This article introduces how to effectively manage windows on mac.

As a software engineer, you might need to open up multiple windows at the same time when doing development work, e.g., a terminal window, a browser, a IDE window, a chat application, etc. You might also have multiple monitors as well. Window management becomes a neccessary part of your work. Unfortunately, mac doesn’t come with a decent solution to this.

In this article, I’ll talk about how you can effectively manage your windows on your mac. I’ll cover two topics specifically:

  • How to switch between windows?
  • How to place the target window on the desired position?

Window Switching

Switching between windows is probably one of the most common operations you’ll need when using any kind of operating system.

If you’ve used a Windows Operating System before, you’ll know that it provides a pretty good solution to switch among windows, which is ctrl + tab. Whenever you press ctrl + tab, you’ll see a window poping up in the middle of your screen, presenting you all the current visible windows. By selecting the target window, you can switch to it pretty easily.

This feature works really well on Windows, and Linux also provides the same feature. However, for some reason, mac doesn’t do that by default. Mac has a cmd + tab hotkey, and it pops up a similar window. However, that window presents active applications in your system, and those active applications might include ones without a window. In most cases, you won’t care about them, but it is annoying. I typically only have a few windows opened when I’m working on mac. I’d like to see a clean list, instead of a whole bunch of background running applications, which makes windows switching a lot more inefficient.

Over years, I’ve always tried to find softwares that can provide similar feature as Windows and Linux do. I found two of them work really well.

HyperSwitch

A few years ago, I found this software called HyperSwitch, and it does exactly what I want. It works pretty well, just like Windows and Linux.

HyperSwitch has been pretty reliable before I switched to use the M1 macbook air from my x86 based macbook pro. On M1, I’ve encounter cases where HyperSwitch just failed to find certain windows. I’m not sure why that happened. I also noticed that HyperSwitch is not under active maintenance anymore. The last time it was updated was before 2021. Then I started to search for other solutions.

AltTab

AltTab is another software that provides similar feature. It pretty much does the same thing, but it has richer features. AltTab is also opened sourced at github.

I started to use AltTab ever since 2021. It works pretty good, and I’m still using it now. AltTab is definitely my recommended solution to mac window switching.

Window Placement

Another big topic of window management is window placement. If you’ve used Windows before, you’ll noticed it provides a few pretty handy window management hotkeys:

  • win + left: Place window on the left part of the screen.
  • win + right: Place window on the right part of the screen.
  • win + up: Maximize the window.
  • win + down: Restore the previous window position if it is currently maximized, otherwise, minimize the window.

I wish mac had come with these features by default, but it had never came. Then I started to search solutions for that.

There are tools to do that, e.g., Magnet, BetterSnapTool, HazeOver, etc. But as a software engineer, I definitely can’t accept these solutions, because they not customizable enough.

And then I found hammerspoon, which is truely a game changer on mac. Hammerspoon is not a window management tool. Instead, it is a powerful automation framework on mac. Hammerspoon connects a lot of system APIs with lua script. In other words, you can use lua script to call system APIs to implement your feature.

This is a great tool for us to define some window management tricks. Based on that, I wrote a script to help me place windows. It is similar to the one on Windows and Linux, but there is a little bit difference.

  • win + left: Place window on the left part of the screen. If your window is already on the left side of the screen, it will be moved to the monitor to the left (if there is any).
  • win + right: Place window on the right part of the screen. If your window is already on the right side of the screen, it will be moved to the monitor to the right (if there is any).
  • win + up: Maximize the window.
  • win + down: Place the window in the center.

Window placement with hammerspoon is definitely the most preferred solution to me. I attached the script on the bottom. To use it, install hammerspoon (follow this page) first, then copy the following content to ~/.hammerspoon/init.lua, and restart hammerspoon.

------------------------------------------------------------------------------------------
-- The following script comes from https://devmemo.io/blog/mac_window_management/
------------------------------------------------------------------------------------------

local devmemo_config = { }


------------------------------------------------------------------------------------------
-- Window Management
------------------------------------------------------------------------------------------

function devmemo_config.focused_window()
   return hs.window.focusedWindow()
end

function devmemo_config.set_frame(unit)
   return devmemo_config.focused_window():setFrame(unit, 0)
end

function devmemo_config.maximize_window()
   local sf = devmemo_config.focused_window():screen():frame()
   devmemo_config.set_frame({ x = sf.x, y = sf.y, w = sf.w, h = sf.h })
end

function devmemo_config.center_window()
   local sf = devmemo_config.focused_window():screen():frame()
   devmemo_config.set_frame({ x = sf.x + sf.w / 4, y = sf.y + sf.h / 4, w = sf.w / 2, h = sf.h / 2 })
end

function devmemo_config.send_window_left()
   local sf = devmemo_config.focused_window():screen():frame()
   local old_wf = devmemo_config.focused_window():frame()
   devmemo_config.set_frame({ x = sf.x, y = sf.y, w = sf.w / 2, h = sf.h })

   if old_wf == devmemo_config.focused_window():frame() then
      local west_screen = devmemo_config.focused_window():screen():toWest()
      if west_screen ~= nil then
         local wsf = west_screen:frame()
         devmemo_config.set_frame({ x = wsf.x + wsf.w / 2, y = wsf.y, w = wsf.w / 2, h = wsf.h })
      end
   end
end

function devmemo_config.send_window_right()
   local sf = devmemo_config.focused_window():screen():frame()
   local old_wf = devmemo_config.focused_window():frame()
   devmemo_config.set_frame({ x = sf.x + sf.w / 2, y = sf.y, w = sf.w / 2, h = sf.h })

   if old_wf == devmemo_config.focused_window():frame() then
      local east_screen = devmemo_config.focused_window():screen():toEast()
      if east_screen ~= nil then
         local esf = east_screen:frame()
         devmemo_config.set_frame({ x = esf.x, y = esf.y, w = esf.w / 2, h = esf.h })
      end
   end
end


------------------------------------------------------------------------------------------
-- Key Bindings and Taps
------------------------------------------------------------------------------------------

hs.hotkey.bind({"cmd"}, "Left", function() devmemo_config.send_window_left() end)
hs.hotkey.bind({"cmd"}, "Right", function() devmemo_config.send_window_right() end)
hs.hotkey.bind({"cmd"}, "Up", function() devmemo_config.maximize_window() end)
hs.hotkey.bind({"cmd"}, "Down", function() devmemo_config.center_window() end)