Windows List, Titles, Move & Size

Mac OSX specific forum
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Windows List, Titles, Move & Size

Post by grabiller »

Ok, so I'm continuing my investigations here about how to retrieve a list of Windows, their titles, and how to move and/or resize them on Mac OSX.

Danilo gave me this snippet as a starting point:
Danilo wrote:
Here is the start to enum the windows and get the PID:

Code: Select all

ImportC ""
    CGWindowListCreate(_1, _2)
    CGWindowListCreateDescriptionFromArray(arr)
    CFArrayGetCount(arr)
    CFArrayGetValueAtIndex(arr, index)
    CFRelease(arr)
    CFDictionaryGetValue(_1,_2)
    CFStringCreateWithCharacters(alloc,text.p-Unicode,len)
    CFNumberGetValue(_1,_2,_3)
EndImport


;CFArrayRef
WindowList = CGWindowListCreate(1, 0)
Debug WindowList
DescriptionList = CGWindowListCreateDescriptionFromArray(WindowList)
Debug DescriptionList
Debug "--------------"
If DescriptionList
    count = CFArrayGetCount(DescriptionList)
    If count
        For i = 0 To count-1
            id = CFArrayGetValueAtIndex(DescriptionList,i)
            Debug "ID: "+id
            pid_num = CFDictionaryGetValue(id, CFStringCreateWithCharacters(0,"kCGWindowOwnerPID",17))
            If pid_num
                CFNumberGetValue(pid_num,9,@pid.l)
                Debug "PID: "+pid
            EndIf
        Next
    EndIf
EndIf
CFRelease(DescriptionList)
CFRelease(WindowList)
It is pretty straightforward to translate the code, so I hope you can do the rest yourself. I'm having a beer or two now (after returning from nightshift work),
not in the mood for doing all the work for you. It is boring because PB does not import any of this API functions, but it should be doable. ;)

Press ALT+CMD+ESC to terminate the running processes, if it does not exit correctly (possibly some clean-up missing). :D
I've added this code below the "pid_num" section to retrieve the windows titles:

Code: Select all

            nameRef = CFDictionaryGetValue(id, CFStringCreateWithCharacters(0,"kCGWindowName",13))
            If nameRef
              namePtr = CFStringGetCStringPtr( nameRef, #kCFStringEncodingUTF8 )
              If namePtr
                name$ = PeekS( namePtr, -1, #PB_UTF8 )
                Debug "-> NAME: "+name$
              EndIf
            EndIf
Unfortunately it does not work in my case as Lightwave seems to not set the "Quartz window name" and I get empty strings.

From the Apple SDK:
kCGWindowName
The key that identifies the name of the window, as configured in Quartz. The value for this key is a CFStringRef type. (Note that few applications set the Quartz window name.)
Obviously the Lightwave windows show titles nonetheless, so I guess there must be another way to retrieve it somehow.

Any idea ? Another API I should use ?
guy rabiller | radfac founder / ceo | raafal.org
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: Windows List, Titles, Move & Size

Post by Danilo »

All codes use the Mac OS X Accessibility API, and require special permissions.

Played a little bit with the last code (to get some informations):

Code: Select all

ImportC ""
    CGWindowListCreate(_1, _2)
    CGWindowListCreateDescriptionFromArray(arr)
    CFArrayGetCount(arr)
    CFArrayGetValueAtIndex(arr, index)
    CFRelease(arr)
    CFDictionaryGetValue(_1,_2)
    CFStringCreateWithCharacters(alloc,text.p-Unicode,len)
    CFStringGetCharactersPtr(string)
    CFNumberGetValue(_1,_2,_3)
    CGSCopyWindowProperty(_1,_2,_3,_4)
    CGSDefaultConnectionForThread()
EndImport

WindowList = CGWindowListCreate(0, 0)
Debug WindowList
DescriptionList = CGWindowListCreateDescriptionFromArray(WindowList)
Debug DescriptionList
Debug "--------------"
If DescriptionList
    count = CFArrayGetCount(DescriptionList)
    If count
        Debug "found "+count+" windows."
        Debug "--------------"
        For i = 0 To count-1
            id = CFArrayGetValueAtIndex(DescriptionList,i)
            Debug "ID: "+id
            pid_num = CFDictionaryGetValue(id, CFStringCreateWithCharacters(0,"kCGWindowOwnerPID",17))
            If pid_num
                CFNumberGetValue(pid_num,9,@pid.l)
                Debug "  PID: "+pid
                runningApp = CocoaMessage(0,0,"NSRunningApplication runningApplicationWithProcessIdentifier:",pid)
                Debug "  APP: "+runningApp
                If runningApp
                    lName = CocoaMessage(0,runningApp,"localizedName")
                    If lName
                        Debug "  APP.localizedName: "+PeekS(CocoaMessage(0, lName, "UTF8String"), -1, #PB_UTF8)
                    EndIf
                    bID = CocoaMessage(0,runningApp,"bundleIdentifier")
                    If bID
                        Debug "  APP.bundleIdentifier: "+PeekS(CocoaMessage(0, bID, "UTF8String"), -1, #PB_UTF8)
                    EndIf
                EndIf
                ; // Get the app's base window controller
                ;If AXAPIEnabled() And AXIsProcessTrusted()
                ;    AXObserverCreate(pid,@myCallback(),@observer)
                ;    AXUIElementRef WindowHandle = AXUIElementCreateApplication(pid);
                ;EndIf
            EndIf
            win_num = CFDictionaryGetValue(id, CFStringCreateWithCharacters(0,"kCGWindowNumber",15))
            If win_num
                CFNumberGetValue(win_num,3,@win.l)
                Debug "  WIN: "+win
                
                If connection=0
                    connection = CGSDefaultConnectionForThread()
                EndIf
                ; undocumented
                ; http://stackoverflow.com/questions/1606321/how-do-i-get-a-list-of-the-window-titles-on-the-mac-osx
                CGSCopyWindowProperty(connection, win, CFStringCreateWithCharacters(0,"kCGSWindowTitle",15), @titleValue)
                If titleValue
                    Debug "  Window Title: "+PeekS(CocoaMessage(0, titleValue, "UTF8String"), -1, #PB_UTF8)
                EndIf
            EndIf
            Debug "---------------"
        Next
    EndIf
EndIf
CFRelease(DescriptionList)
CFRelease(WindowList)
Enum running apps:

Code: Select all

appList_NSArray = CocoaMessage(0,CocoaMessage(0,0,"NSWorkspace sharedWorkspace"),"runningApplications")
If appList_NSArray
    count = CocoaMessage(0,appList_NSArray,"count")
    Debug "found "+count+" applications"
    Debug "---------------"
    For i = 0 To count-1
        application = CocoaMessage(0,appList_NSArray,"objectAtIndex:",i)
        If application ; NSRunningApplication
            Debug "  APP: "+application
            pid = CocoaMessage(0,application,"processIdentifier")
            Debug "  PID: "+pid
            lName = CocoaMessage(0,application,"localizedName")
            If lName
                Debug "  APP.localizedName: "+PeekS(CocoaMessage(0, lName, "UTF8String"), -1, #PB_UTF8)
            EndIf
            bID = CocoaMessage(0,application,"bundleIdentifier")
            If bID
                Debug "  APP.bundleIdentifier: "+PeekS(CocoaMessage(0, bID, "UTF8String"), -1, #PB_UTF8)
            EndIf
            If pid
                ; use the PID
            EndIf
            Debug "---------------"
        EndIf
    Next
EndIf
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Windows List, Titles, Move & Size

Post by grabiller »

Excellent, thanks a lot Danilo!

Before you posted your message I almost had it, I had this code:

Code: Select all

connection = CGSDefaultConnectionForThread()

NSCountWindows(@windows_count.i)
Debug "=> Windows Count: "+ windows_count

*winMem = AllocateMemory(windows_count*SizeOf(Integer))
NSWindowList( windows_count, *winMem )
*p = *winMem
i = 0
While i < windows_count
  
  CGSCopyWindowProperty( connection, PeekI(*p), CFStringCreateWithCharacters(0,"kCGSWindowTitle",15), @titleRef.i )
  
  If titleRef
    
    titlePtr = CFStringGetCStringPtr( titleRef, #kCFStringEncodingUTF8 )
    If titlePtr
      title$ = PeekS( titlePtr, -1, #PB_UTF8 )
    EndIf
    
    If Len(title$) > 0
      Debug "-> TITLE: "+ title$
    EndIf

  EndIf
    
  *p + SizeOf(Integer)
  i + 1
Wend
FreeMemory(*winMem)
But I couldn't get the right windows titles, apparently due to my use of CFStringGetCStringPtr, I'm not sure why because I got "some" windows titles but not all.. In your code you use CocoaMessage to retrieve the title and with that it works, I get all the correct windows titles!

Code: Select all

connection = CGSDefaultConnectionForThread()

NSCountWindows(@windows_count.i)
Debug "=> Windows Count: "+ windows_count

*winMem = AllocateMemory(windows_count*SizeOf(Integer))
NSWindowList( windows_count, *winMem )
*p = *winMem
i = 0
While i < windows_count
  
  CGSCopyWindowProperty( connection, PeekI(*p), CFStringCreateWithCharacters(0,"kCGSWindowTitle",15), @titleRef.i )
  
  If titleRef
    
    title$ = PeekS(CocoaMessage(0, titleRef, "UTF8String"), -1, #PB_UTF8)
    
    If Len(title$) > 0
      Debug "-> TITLE: "+ title$
    EndIf

  EndIf
    
  *p + SizeOf(Integer)
  i + 1
Wend
FreeMemory(*winMem)
Next step: From here, then how to:
1) get the position/size of a window
2) move/resize that window ?

I've found some exemples on stackoverflow as well but those are using Carbon API and comments says the exemples are now deprecated.

I suppose that, if we can get a list of windows with NSWindowList, there must be some NSxxxx functions to move and/or resize the windows we get from that list (or not ?).

By looking at the source code of Spectacle I've found things like:

Code: Select all

[frontMostWindowElement setValue: windowRectSizeRef forAttribute: kAXSizeAttribute];
But I'm not sure how to translate this in PB and if we can directly use the windows id returned by NSWindowList to send this kind of "message".

So I have to experiment from here. If you have any short answer, don't hesitate.

Thanks again.

Cheers,
Guy.
guy rabiller | radfac founder / ceo | raafal.org
Post Reply