How to get window owner names via CGWindowListCopyWindowInfo

I am trying to get the window owner names via CGWindowListCopyWindowInfo. So far I have managed to get the CFDictionaryRef but I am unable to use the right pointers for CFDictionaryGetValueIfPresent .

This is the method signature: https://docs.rs/CoreFoundation-sys/0.1.4/CoreFoundation_sys/dictionary/fn.CFDictionaryGetValueIfPresent.html

const options: CGWindowListOption = kCGWindowListOptionOnScreenOnly + kCGWindowListExcludeDesktopElements;
const ptr_window_list_info: CFArrayRef = unsafe { CGWindowListCopyWindowInfo(options, kCGNullWindowID) as CFArrayRef };

const count = unsafe { CFArrayGetCount(ptr_window_list_info) };

for i in 0..count-1 {
    let dic_ref = CFArrayGetValueAtIndex(ptr_window_list_info, i) as CFDictionaryRef;
    //let key = CFString::new("kCGWindowOwnerName");
    let b = CFDictionaryGetValueIfPresent(dic_ref, ?, ?);
    println!("window owner name: {}", value);
}

I am new to Rust and would appreciate any help.

You should call it exactly as if you were to call it in C, ie. pass the key (which is itself a pointer), and pass a pointer to a previously-declared pointer, which will be populated with a pointer to the value (if it exists), i.e.:

let mut value: *const c_void = ptr::null();
CFDictionaryGetValueIfPresent(dict, key as *const c_void, &mut value);

By the way, you probably want to loop over the range 0..count. The .. operator creates a half-open interval, its upper bound is exclusive.

1 Like

Thank you for your answer. I have adjusted my code but receiving a crash when executing CFDictionaryGetValueIfPresent.

let key: CFString = CFString::new("kCGWindowOwnerName");
let key_ptr: *const CFString = &key;

let mut value: *const c_void = std::ptr::null();

let b = unsafe { CFDictionaryGetValueIfPresent(dic_ref, key_ptr as *const c_void, &mut value) };

Do you see any reason why it would crash?

Do you get any sort of error message, or is it just segfaulting? I'd run the program under gdb (or whatever debugger you use) and print a backtrace when it intercepts the crash.

1 Like

Okay, seems I had to use NSString instead of CFString. I had a look at the call stack and saw the last call was objectForKey which gave me the clue about the wrong object.

let key = NSString::from_str("kCGWindowOwnerName");
let key_ptr: *const NSString = &*key;

Thanks for the hint to backtrace it. Now I am getting the right pointer :slight_smile:

1 Like

No, that is wrong. More precisely, that's a red herring. NSString and CFString are toll-free bridged – they are basically two ways of referring to the same type.

The problem is that in the first snippet, you are trying to pass a pointer to the key. Why? You don't need that as an object is already represented by a pointer. Chances are the CFDictionary implementation is trying to call its hash method internally, but since the pointer you passed in is wrong (it points to a pointer, not an object!, it tries to interpret some garbage value as the class metadata or a function pointer, and crashes.

Just pass key itself, but that's already what I wrote in the first place when I provided the example. Anyway, it should work as either an NSString or as a CFString. It happened to work in your second snippet because you passed a pointer to the dereferenced key, ie. &*key, which is just key itself.

1 Like

H2CO3, thanks for your detailed answer. I have tried passing key as you described but that gives a compiler error for key as *const c_void:

non-primitive cast: core_foundation::string::CFString as *const std::ffi::c_void

note: an as expression can only be used to convert between primitive types. Consider using the From trait

let key: CFString = CFString::new("kCGWindowOwnerName");
let mut value: *const c_void = std::ptr::null();
let b = unsafe { CFDictionaryGetValueIfPresent(dic, key as *const c_void, &mut value) };

For anyone who is interested, here is the solution: https://stackoverflow.com/questions/60117318/getting-window-numbers-via-cgwindowlistcopywindowinfo-in-rust/60122251#60122251

I had to use let key_ptr: *const c_void = unsafe { std::mem::transmute(key) };

Thank you all for helping.

The type should be CFStringRef. CoreFoundation objects are always accessed through pointers, so trying to get them by value doesn't really make sense or work.

If an immediate cast fails, you can use an intermediate cast through *const () or *const u8 or something like that (although look it up in the Nomicon, because I'm by no means an unsafe code expert). Don't use transmute, it's incredibly unsafe, and I'm not at all sure its usage is any correct here.

After I have added use core_foundation::base::*; I have been able to use the trait implementation ToVoid. I understand your criticism regarding transmute and have updated my code.

use core_graphics::display::*;
use core_foundation::string::*;
use core_foundation::number::*;
use core_foundation::base::*;
use std::ffi::{ CStr, c_void };

fn main() {
    const OPTIONS: CGWindowListOption = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
    let window_list_info = unsafe { CGWindowListCopyWindowInfo(OPTIONS, kCGNullWindowID) };
    let count = unsafe { CFArrayGetCount(window_list_info) };

    for i in 0..count {

        let dic_ref = unsafe { CFArrayGetValueAtIndex(window_list_info, i as isize) as CFDictionaryRef };

        let key = CFString::new("kCGWindowOwnerName");
        let mut value: *const c_void = std::ptr::null();
    
        if unsafe { CFDictionaryGetValueIfPresent(dic_ref, key.to_void(), &mut value) != 0 } {
    
            let cf_ref = value as CFStringRef;
            let c_ptr = unsafe { CFStringGetCStringPtr(cf_ref, kCFStringEncodingUTF8) };
            if !c_ptr.is_null() {
                let c_result = unsafe { CStr::from_ptr(c_ptr) };
                let result = String::from(c_result.to_str().unwrap());
                println!("{}", result)
            }
    
        }

    }

    unsafe {
        CFRelease(window_list_info as CFTypeRef)
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.