Cascading or Otherwise Aligned Windows

A brief update and further help request:

As a start, I tried to convert your nacre into a subroutine so that I could use it in different macros:

  1. The macro is here:
Summary

Prompt Window Grid Subroutine.kmmacros (22.7 KB)

  1. As you can see from the below, multiple windows with the same title (Process Downloads) are being spawned (there are actually three in total at the same time):

  1. The debugs show that Local_TheList and Local_WindowTitle are properly being passed.

  2. When I drop 3 files creating three concurrent windows, the AppleScript's output is three blank debug windows (no co-ordinates are being reported), all with the identical unix time stamp, as though one of the spawned windows are being recognized and, yes, all three spawned windows exist at the same time.

  3. With the above results the co-ordiantes for Local_Corner[1] and Local_Corner[2] are always the first item in Local_TheGrid, not what we wanted.

PS. The identical Unix Timestamps got me think that I may have trace condition on my hands so I tried a Semaphore Lock, no difference. I am stumped!

Please help me solve the AppleScript problem with the hope that will get things working!

Much thanks!

There is no Local_TheList, and you never use Local_WindowTitle.

But we do need to see what you're passing in to Local_TheGrid.

Do you mean Local_CurrentCorners is empty?

What happens if you drop one file, then another, then another?

I suspect that when you drop 3 files at once you are running 3 concurrent instances of your main macro, the 3 sub instances are being spawned at the same time, so each of them sees no windows. It's the main macro that's the problem and we need to see that too.

Edit to add:

Here's the subroutine version of my original -- as you can see, it's a very simple conversion:

SUB - First Empty Grid Cell.kmmacros (13.7 KB)

@Nige_S , as a start a huge thank you...it took all morning to sort out (good thing I work for myself) but I finally got working perfectly. NEVER woudl ahem done this without you.

The final macro is below noting I added extensive comments inc case anyone wants to follow!

Macro Image

Module_Determine Free Window Position.kmmacros (49.8 KB)

The problem with calling macro creating multiple instances was that threre was not enough time between the window being spawned and the determination / measurement of whether the window existed (i.e., window could not be spawned fast enough). Call this Problem A.

The second problem I ran into was more instances than the grid / reserved window positions. Call this Problem B.

To show I dealt with these two issues I also include the parent macro where aqua actions denote actions to solve Problem A while yellow actions to denote sections to solve Problem B. I have again included teh actual macro as the commenting is extensive.

Macro Image

Process Downloads.kmmacros (96.5 KB)

The result when run fro two different calling macros is:

Worth noting once setup it cane be added to the calling macro in about 5 minutes as the action can be simply copied and pasted, the only think to change is the window title to be searched for.

Once again, huge thanks!

Edit to add:
I've just realised how much I've written.

That's me being very picky, and no reflection on the quality of your macros -- I'd be very pleased if I'd written these!

Anyway, random thoughts follow...

Do you need such an early semaphore lock? It looks like there's no possible contention until the subroutine, so why not lock immediately before that?

Why the repeat on the sub call? Does the sub ever return an empty string?

BTW, the "Break" Action in the sub does nothing except take up space on your drive and time loading the macro -- DUCY?

Flipping between the two different AppleScript methods of getting the instance's Local variables would do my head in! Also, there's a slim chance that you exceed the environment limit and system attribute KMVAR_Local_myVar returns an empty string when it shouldn't.

Since it is "regular", consider building your grid with a "Repeat" Action -- it'll be execute a few milliseconds slower, but it'll be much quicker to change the number of cells or the offsets! You could even make it a subroutine :wink:

It's good idea you've had for pausing until the prompt spawns, but it's not an efficient implementation (even leaving aside that you have to instantiate the AS for every check). At the very least -- if you are only returning "1" or "0", use a straight "is: 1" Condition instead of a regex.

But I think your AS could be changed to:

set inst to system attribute "KMINSTANCE"

tell application "Keyboard Maestro Engine"
	set winTitle to getvariable "Local_PromptProgressWindowTitle" instance inst
	set wantCorner to getvariable "Local_WindowXY" instance inst
end tell

set AppleScript's text item delimiters to ","
set theCorner to {text item 1 of wantCorner as number, text item 2 of wantCorner as number}

tell application "System Events"
	tell process "Keyboard Maestro Engine"
		repeat
			try
				if (every window whose (name is winTitle and position is theCorner)) is not {} then return
			on error
				-- do nothing
			end try
			delay 0.1
		end repeat
	end tell
end tell

...in a simple "Execute AppleScript" Action, no KM repeat or pause required.

I hope not! Group the Actions, add that Group to Favourites, add from there!

All good, it is greatly appreciated as it is teh best way for me to learn!

Absolutely not! I placed it there for "convenience" so it would be easy to notice. If you look at the comment that precedes the semaphore it specifically states that it can be placed immediately before the HTML Prompt.

I am not sure I understand the question but will do my best.

Yes the subroutine does return an empty string when the number of instances exceeds the number of windows in teh grid. This is why Step 4. is wrapped in an Until loop, to wait until a free position in the grid becomes available.

Agreed and noted in the comments. Purposely left there to remind me should I ever incorporate it directly into a macro.

I will look into this and report back.

I will look into this and report back.

I will look into this and report back.

I hope not! Group the Actions, add that Group to Favourites, add from there!
[/quote]

Agreed and what has been done BUT the placement must be checked, the new grid created and few test runs performed.

AS ALWAYS, HUGE THANKS!

@Nige_S

Thank you for the review — all three points were well made and all three are now implemented and tested. Reporting back in case it's useful to anyone else.

1. The two AppleScript methods

You were right that this would do anyone's head in, and the environment limit turned out to be less remote than "slim chance" suggests. Both callers handle document content and one handles OCR output, so the 100K aggregate is genuinely reachable — at which point a 17-character window title gets excluded through no fault of its own, the subroutine searches for a window titled empty string, reports every position free, and every window stacks. That is bit-for-bit the original bug, which had already cost me an evening.

Standardised on getvariable with the instance parameter throughout. Only KMINSTANCE still comes from the environment.

2. Building the grid with a loop

Done, and made a subroutine as you suggested — Module_Create Grid Positions, taking an original, a step and a count. With two callers now sharing the pattern it earns its place immediately: changing a cascade is one parameter instead of six literal lines across two macros.

For Each over a number range 1 to Count, with the multiplier as (Local_i - 1) so the arithmetic stays visible in the value field rather than hiding in the range field.

3. Moving the polling loop inside the AppleScript

The strongest of the three, and adopted with two changes.

I kept the Engine's own dictionary rather than System Events. Testing here, System Events returned an empty window list for the Engine process while the Engine dictionary returned everything — probably a permissions thing on my machine, but the Engine route is proven and skips the accessibility layer.

I also added an exit: your loop has no upper bound, so a window that never appears would hold the semaphore indefinitely. It now self-limits at 150 checks (15s) and returns 0, with a 20s action timeout as a backstop that should never fire.

On "is 1" versus the regex — you were right. It went in at the same time as the getvariable fix and I never isolated which mattered. The question is moot now, since there's no condition test left.

One thing I decided against

Separately I considered passing a list of window titles so the two grids could see each other's windows. Rejected it: the subroutine is meant to serve many callers, and a rule requiring every caller to know every other caller's title gets violated silently the first time someone adds one. Non-overlapping grids are documented as a precondition instead.

One thing I decided to do

Also turned off the window title bar on the prompts, which removes the drag handle entirely — so a window can't be moved off its position, and the whole class of "someone nudged it and the slot silently freed" goes away.

Macros attached if anyone wants them.

Thanks once again!

Subroutine that creates the grid

Module_Create Grid Positions.kmmacros (17.2 KB)

Subroutine that determines the free grid position

Module_Determine Grid Position.kmmacros (52.2 KB)

Sample macro that calls the subroutine and grid inputs

Process Downloads.kmmacros (99.8 KB)

PS: Although this was enjoyable and worth the effort -- I think it looks fantastic and will be used repeatedly -- it was a ton of work to build!

Probably not that -- I suspect an error, which is being caught by your try block so is non-obvious.

I went the System Events route because whose clauses are a faster way to filter lists than iterating and testing each element -- not really an issue here, but a) habit and b) I like whose :wink:

But you can't test item 1 of bounds and item 2 of bounds within a whose (at least, I can't ever find a way) -- we need a single coordinate to test against.

The KME Dictionary returns bounds for the window -- but going the System Events/KME process route its us get position, which is exactly what we want.

As I say, speed of a big list isn't an issue here so I'd probably use your method too -- except with a tweak. You're repeatedly splitting b to test against wantX and wantY but it will be quicker (and, IMO, more obvious in the code) if you combine wantX and wantY once then test against that. Demo:

set bList to {{1, 2}, {3, 4}}
set wantXY to "1,2"

set AppleScript's text item delimiters to ","
set wantBoth to {text item 1 of wantXY as number, text item 2 of wantXY as number}

repeat with b in bList
	if contents of b = wantBoth then return true
end repeat

return false

Note the explicit dereference in contents of b.

Well, for 99 hours anyway :wink: You could control this with the Action's timeout but, IMO, your way is both better and more obvious.

Thanks Nige — the whose explanation is the useful bit I was missing. I'd assumed you'd gone the System Events route by preference; I hadn't realised it was because you can't test a component of a list property inside a whose, so filtering on coordinates needs position rather than bounds. That makes the choice a real one rather than a habit, and I'll remember it next time I want the filtering done app-side.

On the error-versus-permissions question, you may well be right in general — a swallowed error inside a try is exactly the kind of thing that looks like "empty" rather than "broken". In this instance the test was a bare one-liner in Script Editor with no try around it, and it came back as an empty list rather than raising, so I suspect permissions on this machine. Either way the Engine route works, so I've left it there.

The point about contents of b is well taken — I hadn't appreciated that repeat with b in bList hands back a reference rather than a value. Filed away.

I'm going to leave the comparison as it stands, though. Three windows means the saving is microseconds and the current version is tested, so I'd rather not disturb it. Not a criticism of the suggestion, just not worth the churn at this scale.

And fair correction on "indefinitely" — 99 hours, not forever.

Appreciate you taking the time on all of this. All three of your original points were worth making and the macros are better for them.

It won't be permissions -- but we'll never know what it is until you post the failing script :wink:

Fair enough — and you were right to push, perhaps not quite in the direction you expected.

Re-ran it. Both in Script Editor, three Process Downloads windows plus several Display Text windows on screen.

AppleScript

tell application "Keyboard Maestro Engine" to get name of every window
{"Process Downloads", "Keyboard Maestro - Display Text", "Keyboard Maestro - Display Text", "Keyboard Maestro - Display Text", "Keyboard Maestro - Display Text"}

AppleScript

tell application "System Events" to tell application process "Keyboard Maestro Engine" to get name of every window
{}

So not an error and nothing swallowed — no try anywhere, no dialog. Accessibility just reports the Engine process as having no windows, while the Engine's own dictionary lists five.

I don't know what to make of that. Whether it's specific to this machine or to something about how those windows are created, I couldn't say.

Both name and title work fine here -- tested on macOS 15.4 and 26.4.1, KM v11.0.4 and 11.1.1, HTML prompts with and without the title showing and run both sync and async, and normal "Display Text" windows...

So yes, looks like something specific to your setup. I'd guess a Privacy & Security setting -- but I'd also expect something in there to be far wider-ranging than just this use case and to have been noticed by you before!

So... ¯\_(ツ)_/¯

You were right that it isn't permissions — and it turns out it isn't a failure either.

Ran it properly from inside KM this time rather than Script Editor:

UI elements enabled: true
Process exists: true
Window count: 2
Names: [PDF Compress | Process Downloads]

So System Events is working perfectly well here. The Script Editor result of {} I still can't account for, but it's evidently a Script Editor quirk rather than anything to do with the Engine or the prompt windows — which makes it a much less interesting problem than I'd made it.

I'd been testing the wrong harness and drawing conclusions about the wrong thing. Apologies for sending you down that path.

I'll stay on the Engine dictionary, but for a better reason than before: one fewer layer, and no dependency on Accessibility remaining granted. Your position point stands as the reason to reach for System Events when the filtering matters.

1 Like