Bug in Scripting Engine Variables

The Expectation

Here's a simple function to calculate the distance of the active window from the top of the active monitor (i.e. the one that it's on)...

async function getNumber(varName) {
	return await get_number_variable({
		variable_name: varName
	});
}

async function distanceFromTop() {

	let focused_screen_y = await getNumber("focused_screen_y")
	let focused_window_y = await getNumber("focused_window_y")

	return `${focused_screen_y}, ${focused_window_y}, ${focused_window_y - focused_screen_y}`
}

Pretty straight forward. It just gets the y values for both the active window and active screen, then subtracts one from the other.

The Problem

First, here's my current screen layout. Note that the 'primary' display is on the left, not the middle display, and that it's higher than the primary display too.

Now if the active app is on the primary monitor (on the left) or on the right monitor (which is exactly aligned to it to the pixel), as expected, I get 0, 25, 25 for both, with the delta of 25 representing the height of the menu bar.

However, when the app is on the middle monitor, I unexpectedly get -346, -349, -3 which is clearly wrong! While it is expected both y values would be negative since the display is higher than the primary one, the delta definitely shouldn't be negative as that would mean the window would be off the top of the screen!

Retina vs Native Resolutions

As an experiment, I switched my monitor from 'full 4k res' to 'retina' making it appear as a 1920x1080 screen which resulted in the following layout.

Again, note the middle display is still higher than the 'primary' one so again, it's y value should be negative. But when I tried it with that resolution, I now get even wilder numbers... 734, -349, -1083! Again, the window is right up against the top edge. I even moved it down a little, and now got 734, -336, -1070 (13 px lower) so it is picking up that it moved. I just have no idea what it's relative to being that the delta is now wildly over negative 1000!

Not sure how to work around this so I'll have to abandon my scripting efforts for now until there's either a fix, or if this is a 'me' error, you can help me figure out what I'm doing incorrectly.

unfortunately macOS coordinates are very confusing due to different coordinate systems being used for different things. For these variables BTT passes the raw values without converting them.

The window origin is relative to the top left corner of the main screen (the one with the menubar in your screenshot). Here anything higher than the menubar will be negative, everything lower will be positive.

The screen origin however is relative to the bottom left corner of the main screen and everything higher than that will be positive - everything lower negative..

To normalize these coordinate systems you'd need to add the height of the main screen to focused_screen_y (or substract it from focused_window_y). Unfortunately that height is currently not available in the BTT variables (it could be accessed via active_screen_resolutions).

I think it would make sense to align these coordinate systems in BTT, I'll check how I can do that without breaking existing setups.

So basically you'd probably want this:

async function getNumber(varName) {
	return await get_number_variable({
		variable_name: varName
	});
}

async function distanceFromTop() {
	let focused_screen_height = await getNumber("focused_screen_height")

	let focused_screen_y = await getNumber("focused_screen_y")
	let focused_window_y = await getNumber("focused_window_y")

	return `${focused_screen_y}, ${focused_window_y}, ${focused_window_y - focused_screen_y - MAIN_SCREEN_HEIGHT + focused_screen_height}`
}

//edit: 5.074 will add a focused_screen_window_coordinates_y variable. If you replace focused_screen_y with that one your original script should work.

HA! Don't know if I mentioned, but I actually work for Apple so I'm verrrrry versed in the different coordinate systems and the frustrations that ensue. Drives me nuts when trying to draw to the screen vs a window vs a PDF. (Do not get me started about manual text rendering!!! Believe me... I feel your pain!)

So yeah... now that you said you're passing back the raw values (I incorrectly assumed you were doing the calculations for us based on your naming), it makes complete sense, and you're right... without having access to the primary screen, I'm DOA from doing this from my end.

One possible solution would be to introduce new left/right and top/bottom variants. Not only would that remove any ambiguity of what they actually represent, but it would also be a single call to get the right or bottom edges instead of the two it takes now (i.e. getting x, then the width and adding them to get the right side.) The added bonus is being new variables, they can't affect existing setups/workflows.

Better yet, introduce bounds variants (e.g. active_window_bounds or focused_screen_bounds) that return them all at once in a JSON object. After all, the math needed to calculate all of them is definitely less expensive than four (or eight!) separate async calls. They could even be functions instead of vars (e.g. getActiveWindowBounds(), getMouseScreenBounds(), etc.) so you'd only do those calculations when explicitly asked for.

We'd then get back something like this...

{
    x: 12,
    y: 34,
    width: 1024,
    height: 768,
    left: 12,
    top: 34,
    right: 1036,
    bottom: 802
}

Going further, something else that would be incredibly helpful is exposing the values you're already using for your own 'snapping' window-management calls (i.e. the bounds you're using for top-left-corner or bottom-half, or maximized, etc.) These could be returned via a function that takes an enum/constant representing the particular pre-defined snapping area we're interested in.

I'm suggesting this as these aren't things are easily calculable from our side. They aren't just dividing up the screen. Your logic takes into consideration things like the menu visibility, the dock visibility, even your settings with the 'empty space between window edges` and 'no padding for maximized windows' options.

If we could get the same values you already have/are using, not only would it simplify things for us in scripting, but they would be truly consistent with your own as well instead of guesstimations or 'ranges' as I'm doing now.

Of course what I really want, and the entire point of all of this is just to create a toggle_maximized command that switches between the current window size and the maximized size. There too you already have 90% of what's needed considering you already have an option to restore the window's size if you manually move it after maximizing it. This would just add the position too, and be triggered via a command rather than the mouse.

Anyway, fellow dev over-engineering things here, but hopefully if nothing else, it's at least giving you some ideas for improvements as well as giving you more insight into how us customers are using your incredibly awesome product.