Select and tile windows

Hi guys.

I'm a hammerspoon user trying to migrate to KM.

One workflow in hammerspoon I've found very useful is something I've written myself. I do the following:

  1. Using HS, I can get information about the front most window. I configure a hotkey to do this and add it to a lua table (essentially an array).
  2. When I've added enough windows to this array, I press another hotkey. Doing so does this:
    a. Calculate screen width.
    b. Divide screen width by the size (number of windows in) my array.
    c. Iterate through the array, and for each window within, set it's width to the number calculated in (b), and it's position to be just beside the last positioned window from the array.

What this effectively does is allow me to select an arbitrary number of windows, and then tile them evenly across the screen. I use a 43" monitor at home, so this gives me a quick workspace for whatever I'm working on.

Is this able to be accomplished in KM? If so, could you give me some quick pointers to the actions I should consider?

Thanks in advance!

I see that I can use a "Set Variable" with "SCREENVISIBLE(Main, Width)" to get the screen width. I think the most I'll need help with is selecting a set of windows and iterating through them, and positioning the relative to each other.


This macro has a tile function that gives you most of what you want.
WIndowDual v2.22 posted 10Jun2020 13:07 Wed

Thanks. I'll see if I can't reverse engineer what you've done.

You say you are getting frontmost window and adding it to an array, but it's not clear how you remember which windows are in the array.

In Keyboard Maestro, you could remember the titles, and reference the windows based on titles, but only if they have unique titles. Or you can reference them by index (Z-ordered), but you can't have multiple different front windows without changing the indices.

The rest is straight forward.

Hi there. I guess this has just worked for me in Hammerspoon since I wrote it. Assuming both tools would need to be able to identify the windows, I'm guessing it's using title but I'm storing references to hs.window objects themselves, so without looking at the Hammerspoon Obj-C I'm just speculating.

Here's what the entirety of the Lua code looks like:

tile_apps = {}

function addToTileApps()
  log.w("called addToTileApps")
  front_window = hs.window.frontmostWindow()
  table.insert(tile_apps, front_window)
  list = ""
  for _, app in pairs(tile_apps) do
    log.w("APP: " .. hs.inspect(app))
    list = list .. "[" .. app:title() .. "]\n"
  end"Current tile queue: \n" .. list)
  log.w("App: ", front_window)
  log.w("App: ", hs.inspect(tile_apps))

function tileHorizontally() 
  log.w("called tileHorizontally")"Tiling horizontally")
  local screen = hs.screen.mainScreen()
  local current_width = screen:currentMode().w
  local current_height = screen:currentMode().h
  local table_size = #tile_apps
  local single_width = current_width / table_size

  log.w("Calling alignWindowGroup. current_width: " .. current_width .. 
        " - table_size: " .. table_size ..
        " - single_width: " .. single_width)
  local offset = 0
  for _, v in pairs(tile_apps) do
    log.w("Offset is: " .. offset)
    v:setFrame(hs.geometry(offset, 0, single_width, current_height))
--    log.w("Raising...")
--    v:raise()
    offset = offset + single_width

function clearTileApps() 
  log.w("called clearTileApps")
  tile_apps = {}"Tile queue cleared")

my_bind("3", addToTileApps)
my_bind("4", tileHorizontally)
my_bind("5", clearTileApps)

When you say the rest is straightforward...maybe for you, but as a KM noob possibly not for me.

I think I can figure out the iteration logic, but the movement and relative positioning is different. Specifically, I think I should use "Manipulate a Window" for movement...would you agree? What about for querying window information?


Two questions: First, do you want to use this macro only for windows in one application at a time, or across multiple applications? Second, do you want the windows' height to remain the same, or to stretch the full or half height of the display?

  1. Across multiple applications
  2. I would like the height to stretch/adjust. In the code above:
    v:setFrame(hs.geometry(offset, 0, single_width, current_height))

This says move the window to the calculated offset, set its width to the calculated width, and set the height to the current_height, which is previously set to the height of the current display. So it stretches to fill the screen vertically.

Great, thanks. In that case, I think this should get you most of the way there. To capture window info, you'll need a simple macro with a single Append Variable action that records both the window and app name to a KM variable (though as @peternlewis says above, this does assume that each window you add to this list has a unique name:

Then to use the variable to calculate the window width and iterate through that list, you could do something like this:

Tile Windows.kmmacros (4.8 KB)

The last disabled action resets the window list variable. Assuming you want to erase the list after tiling windows like this, feel free to enable it after ensuring the macro works to your satisfaction.

1 Like Thank you! I will experiment with this.

Assuming I didn't want to reset the window list, but have a separate shortcut which would do that, is there a way to persist or store the WindowList globally?

No problem. Feel free to ask if you have further questions about any particular part.

The WindowList variable is stored globally in a persistent state (whereas all the variables that start with Local are transient and exist only while that macro is running) so it won't go away until you delete it, either by enabling that action, making a new macro that does the same thing, or editing it directly from the Variables pane in KM's preferences.

@d1rewolf, I'm also on a 43" monitor and would love something like this. Did you end up finishing a macro?