Get window ID

I wondered if there is an ASObjC equivalent to the following:

tell application "Safari" to set windowID to id of window 1

The reason I ask is because the following fails (FSNotes is not scriptable):

tell application "FSNotes" to set windowID to id of window 1

The name of the app will be obtained with:

set activeApp to current application's NSWorkspace's sharedWorkspace()'s frontmostApplication()
set activeApp to activeApp's localizedName as text

The window ID will be used with the -l option (capture the window with windowid) of the screencapture utility.

I spent the better part of an hour on this with Google but couldn’t find anything. Thanks.

Thanks Fredrik71 but I don’t think any of those will do what I want.

As you have probably figured out, you can’t get the window ID of non scriptable apps from vanilla AppleScript or AppleScriptObjC code.
But you can from the CoreGraphics and CoreFoundation frameworks, using C or ObjectiveC or Swift.
You can even do it from a Python script, but unfortunately you can’t use these frameworks with AppleScript.

So when the tools you’re using can’t get the job done, you need to make yourself a new tool.
The “GetWindowID” command line tool that “Fredrik71” has pointed you at could be adapted.

And I also have a simpler command line tool that I made a couple of years ago using Swift.
Which could be called from a do shell script command with an application pid as a parameter.

You would need to be familiar with Xcode, in order to compile and build either of the above command line tools, but let me know if you interested, and I’ll post the simple Swift code which could be adapted.
As long as the forum moderators are happy for non AppleScript code postings.

Regards Mark

Mark. Thanks for responding to my post. It’s good to know for certain that I can’t get the window ID of non-scriptable apps exactly as I want.

My existing script calls screencapture in window-select mode when it encounters a non-scriptable app. This works reasonably well and I’ll stick with that. Thanks again.

ASObjC can’t do this, but JSObjC can. Like any AppleScript, it can be run in Script Editor or on the command line with osascript -l JavaScript …

[format]ObjC.import(‘CoreGraphics’);
ObjC.import(‘Quartz’);

nil = $();
$.unwrap = ObjC.deepUnwrap.bind(ObjC),
$.bind = ObjC.bindFunction.bind($);

$.bind(‘CFMakeCollectable’, [ ‘id’, [ ‘void *’ ] ]);
Ref.prototype._nsObject = function () {
return $.unwrap($.CFMakeCollectable(this));
}

const kCGWindows = $.CGWindowListCopyWindowInfo(
$.kCGWindowListOptionAll,
$.kCGWindowNull)
._nsObject();

const Windows = {
visible : kCGWindows.filter( w =>
w.kCGWindowIsOnscreen ),

applications : kCGWindows.filter( w => 
              !w.kCGWindowLayer )

};

Windows.applications;
[/format]

Tested in macOS Catalina

Thanks CK and KniazidisR for responding to my post.

@KniazidisR. I tested your script with 3 apps–Script Geek, FSNotes, and Soulver–and received an error message on all 3 occasions. I tested different window index numbers without success.

@CK. I pasted your script into Script Editor and received the following error message when I ran the script. This error occurred in the first line of the script, so I assume I’m doing something wrong.

I am deleting my post for now as it was useless. It turns out that Handbrake is scriptable, which is why my code worked with it. I was confused by the Script Debugger. It defines this application as non-scriptable.

I tested JsObjC solution of CK on my Mac. It works. Switch ScriptEditor to JavaScript language.

kCGWindowNumber is ID you look for

@KniazidisR. I switched Script Editor to JavaScript language and CK’s script worked fine. Thanks.

@Fredrik71. I appreciate your response to my post. I’d prefer not to install that utility if it can be avoided but if that’s not possible I’ll give it a try. Thanks again.

Following script will retrieve the front process PID, front process Name, its app Name, front window ID, front window Name. I wrote it today.
 

tell application "System Events"
	set frontProcess to 1st process whose frontmost is true
	set activePID to unix id of frontProcess
	set bundleID to bundle identifier of frontProcess
	set activeName to name of frontProcess
end tell

tell application id bundleID
	try
		set appName to name
		set windowID to id of front window
		set windowName to name of front window
	on error
		set windowID to my getWindowID(appName)
		try
			tell application "System Events" to tell frontProcess to tell window 1 to set windowName to value of attribute "AXTitle"
		on error
			set windowName to ""
		end try
	end try
end tell

return {pid:activePID, processName:activeName, appName:appName, windowID:windowID, windowName:windowName}
--> {pid:9893, processName:"Avidemux2.7", appName:"Avidemux_2.7.8", windowID:5969, windowName:"Avidemux"}


on getWindowID(appName)
	set JS to "ObjC.import('CoreGraphics');
Ref.prototype.$ = function() {
    return ObjC.deepUnwrap(ObjC.castRefToObject(this));
}
Application.prototype.getWindowList = function() {
    let pids = Application('com.apple.systemevents')
              .processes.whose({ 'bundleIdentifier':
                    this.id() }).unixId();

    return  $.CGWindowListCopyWindowInfo(
            $.kCGWindowListExcludeDesktopElements,
            $.kCGNullWindowID).$()
             .filter(x => pids.indexOf(x.kCGWindowOwnerPID) + 1
                       && x.kCGWindowLayer     == 0
                       && x.kCGWindowStoreType == 1
                       && x.kCGWindowAlpha     == 1
            ).map(x => [x.kCGWindowNumber]);           
}
Application('" & appName & "').getWindowList();"
	return (word 1 of (do shell script "osascript -l JavaScript -e " & quoted form of JS)) as integer
end getWindowID

 

1 Like

Do you really need the window ID.
I tried on non-scriptable app. But you can’t get the “id”
There’s many other properties you can access, including all the sub-elements.

This script shows how you can get the first window and access it’s properties:

use AppleScript version "2.5" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
--use scripting additions

property windowInfo : missing value

set windowInfo to my getWindowDebugInfoForProcessName:"Notes"

on getWindowDebugInfoForProcessName:aProcessName
	tell application "System Events"
		tell application process aProcessName
			set aProceessProperties to its properties
			log {"aProceessProperties is:", aProceessProperties}
			set aWindow to its first window
			log {"aWindow is:", aWindow}
			set aWindowName to (name of aWindow)
			log {"aWindowName is:", aWindowName}
			set aWindowProperties to (properties of aWindow)
			log {"aWindowProperties is:", aWindowProperties}
			set aWindowAttributes to (attributes of aWindow)
			log {"aWindowAttributes is:", aWindowAttributes}
			set aWindowIdentifier to (value of attribute "AXIdentifier" of aWindow)
			log {"aWindowIdentifier is:", aWindowIdentifier}
			--set aWindowID to (id of aWindow)
			--log {"aWindowID is:", aWindowID}
		end tell
	end tell
	return {aProceessProperties, aWindowProperties, aWindowAttributes}
end getWindowDebugInfoForProcessName:

And the results:

{

{

*class* :*application process* ,

has scripting terminology:***true***,

bundle identifier:"com.apple.Notes",

*file*:*alias* "Kaptin:Applications:Notes.app:",

creator type:"????",

subrole:*missing value* ,

entire contents:{},

selected:*missing value* ,

*application file*:*alias* "Kaptin:Applications:Notes.app:",

orientation:*missing value* ,

role:"AXApplication",

accepts high level events:***true***,

file type:"APPL",

value:*missing value* ,

position:*missing value* ,

id:**167977** ,

displayed name:"Notes",

name:"Notes",

background only:***false***,

frontmost:***false***,

size:*missing value* ,

visible:***true***,

Classic:***false***,

role description:"application",

maximum value:*missing value*,

architecture:"x86_64",

partition space used:**0**,

short name:"Notes",

focused:*missing value* ,

minimum value:*missing value*,

help:*missing value* ,

title:"Notes",

accepts remote events:***false***,

total partition size:**0**,

description:"application",

accessibility description:*missing value*,

enabled:*missing value* ,

unix id:**579**

},

{

*class* :*window* ,

minimum value:*missing value*,

orientation:*missing value* ,

position:{

**22**,

**22**

},

accessibility description:*missing value*,

role description:"standard window",

focused:***false***,

title:"Notes",

size:{

**1188**,

**778**

},

help:*missing value* ,

entire contents:{},

enabled:*missing value* ,

maximum value:*missing value*,

role:"AXWindow",

value:*missing value* ,

subrole:"AXStandardWindow",

selected:*missing value* ,

name:"Notes",

description:"standard window"

},

{

*attribute* "AXFocused" of *window* "Notes" of *application process* "Notes",

*attribute* "AXFullScreen" of *window* "Notes" of *application process* "Notes",

*attribute* "AXTitle" of *window* "Notes" of *application process* "Notes",

*attribute* "AXChildrenInNavigationOrder" of *window* "Notes" of *application process* "Notes",

*attribute* "AXPosition" of *window* "Notes" of *application process* "Notes",

*attribute* "AXGrowArea" of *window* "Notes" of *application process* "Notes",

*attribute* "AXMinimizeButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXDocument" of *window* "Notes" of *application process* "Notes",

*attribute* "AXSections" of *window* "Notes" of *application process* "Notes",

*attribute* "AXCloseButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXMain" of *window* "Notes" of *application process* "Notes",

*attribute* "AXFullScreenButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXProxy" of *window* "Notes" of *application process* "Notes",

*attribute* "AXDefaultButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXMinimized" of *window* "Notes" of *application process* "Notes",

*attribute* "AXChildren" of *window* "Notes" of *application process* "Notes",

*attribute* "AXRole" of *window* "Notes" of *application process* "Notes",

*attribute* "AXParent" of *window* "Notes" of *application process* "Notes",

*attribute* "AXTitleUIElement" of *window* "Notes" of *application process* "Notes",

*attribute* "AXCancelButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXModal" of *window* "Notes" of *application process* "Notes",

*attribute* "AXSubrole" of *window* "Notes" of *application process* "Notes",

*attribute* "AXZoomButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXRoleDescription" of *window* "Notes" of *application process* "Notes",

*attribute* "AXSize" of *window* "Notes" of *application process* "Notes",

*attribute* "AXToolbarButton" of *window* "Notes" of *application process* "Notes",

*attribute* "AXFrame" of *window* "Notes" of *application process* "Notes",

*attribute* "AXIdentifier" of *window* "Notes" of *application process* "Notes"

}

}
1 Like

I tried on 2 non scriptable applications (“Script Geek” and “Avidemux2.7.8”) and my solution worked there. Naturally, they had at least 1 window open before.

May I ask what application the solution did not work with? About the fact that the window ID of a non-scriptable application obtained from the C code will be difficult to use, I agree.

BTW, your script can’t return window ID for process “Avidemux2.7” which is the process for application “Avidemux_2.7.8”

Thanks KniazidisR and technomorph for the posts. I was working on other stuff and must have lost track of this thread.

I tested KniazidisR’s suggestion and it worked with the screencapture window ID option both with apps that are scriptable and not scriptable. Apps in the latter category include FSNotes, Soulver 2, Easy Find, IA Writer Classic, and Epson Scan 2. Thanks for the great solution.

1 Like

It’s usually customary when utilising code written by someone else, or sourced from another site, to credit the source. Of course, no one can force you to, and perhaps one can argue that all code is just a re-hash of stuff that came before it. But, “I wrote it today” feels egregious, as the relevant portion of your code appears lifted from here.

2 Likes

I’m really weak in JXA, and I emphasize this many times, so I searched the net. Your code seemed to me the most suitable (of all the others) for embedding it in AppleScript. Thank you for it and sorry for not stating your original contribution.

But I really wrote the script on the specified day. I sometimes have to refine someone else’s idea in order to get something useful in AppleScript. I’m sure @peavine figured out that the JXA fragment is not mine. Thanks again for it.

I know it fails.
Which is why comment that out. It does get the AXWindowIndentifer

Which is why asked and you never replied why you need the window ID.

So many people fail to post what they are trying to accomplish.

1 Like

technomorph. I needed the window ID for use with the screencapture utility. I stated this in the first post in this thread:

The window ID will be used with the -l option (capture the window with windowid) of the screencapture utility.

BTW, a forum member later suggested an alternate approach, which was to use screencapture’s capture-rectangle option. This works well and is the approach I used in the actual script, which can be found here