Activate (Focus) the First Window (Of Any App) That Contains "Search String"

I hate to tell you this, but the script isn't working for me. It's taking a hugely long time to run, minutes not seconds.

I took the above script and pasted it into an Execute AppleScript action in a new macro. I ran the macro. At the text-field prompt I typed "9" because I happen to know that there is a TextEdit window on my current desktop named "Untitled 9.rtfd". I got the Multicolored Beachball of Death. Repeatedly, even after giving System Events permission through Accessibility to control the computer.

I guessed that it could have something to do with AXRaise, maybe because I'm on Mojave. So I started researching AXRaise.

It turned out that I had let the beachball spin while I was researcing because after a few minutes I got the choice prompt, showing the one TextEdit window. I picked it and it popped to the front. So it's not AXRaise.

And BTW, will this method work to find windows on other desktops (workspaces, not just multiple display screens)?

Never test scripts in Keyboard Maestro, unless its environment is required by the script.

(For instance getting and setting non-global variables.)

Or – you know exactly what you're doing. Otherwise use Script Debugger.

You have nine million windows open – right? It'll take a while.

I just tested on my Mojave system

Have we not covered that ad nauseam? All bets are off when spaces are concerned. System Events' vision is limited – the only way AppleScript can see all app windows is when the app itself is AppleScriptable.

Try this with an error-handler and timeout:

Macro Image
with timeout of 300 seconds
   
   try
      
      --------------------------------------------------------
      # Bring Named Window Forward.
      --------------------------------------------------------
      set searchString to text returned of (display dialog "Enter a Window Name:" default answer "")
      set winNameList to {}
      
      tell application "System Events"
         repeat with theApp in (processes whose background only is false)
            tell theApp
               set appName to its name
               set winList to name of (windows whose name contains searchString)
            end tell
            if length of winList > 0 then
               repeat with winName in winList
                  set contents of winName to (contents of winName) & tab & " --» " & tab & appName
               end repeat
               set winNameList to winNameList & winList
            end if
         end repeat
      end tell
      
      if winNameList ≠ {} then
         
         set theWindow to choose from list winNameList ¬
            with title "Windows With Designated Name" with prompt ¬
            "Pick One:" default items {item 1 of winNameList} ¬
            without empty selection allowed
         
         if theWindow ≠ false then
            set theWindow to item 1 of theWindow
            set AppleScript's text item delimiters to tab & " --» " & tab
            set {theWindow, theApp} to (get text items of theWindow)
            
            tell application "System Events"
               tell process theApp
                  set frontmost to true
                  tell window theWindow
                     perform action "AXRaise"
                  end tell
               end tell
            end tell
            
         end if
         
      else
         beep
      end if
      --------------------------------------------------------
      
   on error errMsg number errNum
      set errMsg to errMsg & linefeed & linefeed & "Num: " & errNum
      if errNum ≠ -128 then
         try
            tell application (path to frontmost application as text) ¬
               to set ddButton to button returned of ¬
               (display dialog errMsg with title ¬
                  "ERROR!" buttons {"Copy Error Message", "Cancel", "OK"} ¬
                  default button "OK" giving up after 30)
            if ddButton = "Copy Error Message" then set the clipboard to errMsg
         end try
      end if
   end try
   
end timeout

If you're looking for sheer speed out of this baby you're SOL – AppleEvents are not well optimized...

Macro Name          : Generic-Test 01
Search String       : “Untitled”
# of Windows Found  : 141
Time of Execution   : 18.831947

Time of Execution includes dialog time for entering the search string and choosing the window to bring forward.

Sorry to Shanghai your evening with a seven-year-old script.

I modified my previous report to include that it eventually did complete. The nien million windows are not on this Desktop. This desktop has eight windows, including one Chrome window with 10 tabs. That doesn't seem unreasonable. Does it have to go through every running app?

Yes, but I keep hoping...

I had been assuming that KBM would let me open a window based on its title, and I hadn't realized that, using standard KBM, I would have to activate the appropriate app first. That was an unpleasant surprise. Trying to work around that lead me to this topic.

Would your script run significantly faster if it was limited to a given list of three or four apps?

Unfortunately you can't limit System Events to a specific desktop – but you can limit it to specific apps.

Change the apps to your preferred list, and run the script. See how long it takes.

set appList to {"BBEdit", "Script Debugger", "Google Chrome", "Finder"}
set winList to {}

tell application "System Events"
   repeat with theApp in appList
      set end of winList to windows of process theApp
   end repeat
end tell

winList

Moving from the window references to something searchable is another story, but this is a start.

I really think you should just accept the limits of vanilla scripting and Keyboard Maestro and either use a dedicated utility – or start asking questions on the Script Debugger Forum about using the window manager with AppleScriptObjC.

That said – @ComplexPoint has updated his JavaScript above to activate the app of the found window.

You might want to play with this a bit:

JavaScript Code
(() => {
    "use strict";

    // Rob Trew @2023
    // Ver 0.03

    // Raise first window with a matching name
    // (also raising its parent application)

    const
        sa = Object.assign(
            Application.currentApplication(),
            {includeStandardAdditions: true}
        ),
        se = Application("System Events"),
        o = ObjectSpecifier(),
        subString = sa.displayDialog("Window name:", {
            withTitle: "Activate window by name",
            defaultAnswer: ""

        })
        .textReturned;

    const
        appWins = se.processes.whose({
            _match: [o.backgroundOnly, false]
        })()
        .flatMap(proc => {
            const
                win = proc.windows.whose({
                    _match: [o.name, {_contains: subString}]
                }).at(0);

            return win.exists()
                ? [[proc, win]]
                : [];
        });

    return 0 < appWins.length ? (() => {
        const [proc, win] = appWins[0];

        return (
            proc.frontmost = true,
            se.perform(win.actions.byName("AXRaise")),
            `${proc.name()} :: ${win.name()}`
        );
    })() : "No matching window found";
})();

Thanks. I appreciate the help. I'll be back to this in a couple of days, I hope...

1 Like

Anything is possible, but that would be a relatively big change, since there are many places that allow you to select an application and I aim for consistency. And from a UI standpoint, it's pretty ugly too, given where there is now a menu there needs to be a menu and room for a variable, actually probably a token field, and its associated popup menu, etc.

This might be an opportunity for tokens.

Given an app name or bundle-id produce a list of windows, and let users create their own UI.

Window list includes an array of {name, id} perhaps.

The least amount of work for you, and it would be orthogonal... :sunglasses:

3 Likes