在 Rust 中通过 CGWindowListCopyWindowInfo 获取 window 所有者名称

Getting window owner names via CGWindowListCopyWindowInfo in Rust

我正在尝试通过 Rust 中的 CGWindowListCopyWindowInfo 获取 window 所有者名称。到目前为止,我已经设法获得了 CFDictionaryRef,但我无法为 CFDictionaryGetValueIfPresent.

使用正确的指针

这是方法签名: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);
}

我是 Rust 的新手,非常感谢任何帮助。

CFDictionaryGetValueIfPresent 看起来像这样:

Boolean CFDictionaryGetValueIfPresent(CFDictionaryRef theDict, const void *key, const void **value);

在core_foundation中是这样的:

pub unsafe extern "C" fn CFDictionaryGetValueIfPresent(
    theDict: CFDictionaryRef, 
    key: *const c_void, 
    value: *mut *const c_void
) -> Boolean

我相信关键是CFStringRef;在 C 中有一个宏可以使它更容易(CFSTR("cstring")),这里我们必须使用稍微长一点的形式:

let c_key = CString::new("kCGWindowOwnerName").unwrap();
let cf_key = unsafe { 
    CFStringCreateWithCString(std::ptr::null(), c_key.as_ptr(), kCFStringEncodingUTF8) 
};

这给了我们 CFStringRef,在 core_foundation 中它是这样定义的:

pub type CFStringRef = *const __CFString;

已经是*const指针了,只是不是我们需要的类型。您可以改为 c_void

let key_ptr: *const c_void = unsafe { std::mem::transmute(cf_key) };

value 参数只需要一个原始的双指针来存储结果。创建一个空指针:

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

并向其传递一个可变引用 (&mut value) 以获得 *mut *const c_void.


你的 rust 代码中有一些错误和奇怪之处。这是一个评论的工作示例:

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

fn main() {
    // CGWindowListOption is a bitmask, combine the flags with bitwise OR
    const OPTIONS: CGWindowListOption = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
    // No need to specify the type or use 'as'; CFArrayRef is the return type from CGWindowListCopyWindowInfo
    let window_list_info = unsafe { CGWindowListCopyWindowInfo(OPTIONS, kCGNullWindowID) };
    // Don't use const here, CFArrayGetCount returns CFIndex (long)
    let count: i64 = unsafe { CFArrayGetCount(window_list_info) };

    for i in 0..count-1 {
        // Here we need the 'as', CFArrayGetValueAtIndex just returns void*
        let dic_ref = unsafe { CFArrayGetValueAtIndex(window_list_info, i) as CFDictionaryRef };

        // Create a CString from the key name we are interested in
        let c_key = CString::new("kCGWindowOwnerName").unwrap();
        // Create a CFString, needs to be released with `CFRelease`. I leave that as an exercise for the reader.
        let cf_key = unsafe { CFStringCreateWithCString(std::ptr::null(), c_key.as_ptr(), kCFStringEncodingUTF8) };

        // cf_key is a CFStringRef, which is a type alias to *const __CFString
        // We transmute it into *const c_void since that is what CFDictionaryGetValueIfPresent wants
        let key_ptr: *const c_void = unsafe { std::mem::transmute(cf_key) };

        // A raw void* to hold the result
        let mut value: *const c_void = std::ptr::null();

        if unsafe { CFDictionaryGetValueIfPresent(dic_ref, key_ptr, &mut value) != 0 } {
            // CFDictionaryGetValueIfPresent returned true; that means value must point to a CFStringRef
            let cf_ref = value as core_foundation::string::CFStringRef;
            // Get a pointer to a C-string buffer with the characters from the CFString
            let c_ptr = unsafe { CFStringGetCStringPtr(cf_ref, kCFStringEncodingUTF8) };

            // The value may be null; don't pass it to CStr::from_ptr
            if c_ptr.is_null() { continue; }

            // Wrap the pointer in a rust CStr so we can convert a str
            let c_value = unsafe { CStr::from_ptr(c_ptr) };
            println!("{}", c_value.to_str().unwrap());
        }
    }
}

作为参考,这里是 C:

中的相同示例
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CGWindowListOption options = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
        CFArrayRef windows = CGWindowListCopyWindowInfo(options, kCGNullWindowID);

        CFIndex count = CFArrayGetCount(windows);

        for (int i = 0; i < count; i++)
        {
            CFDictionaryRef windowDict = CFArrayGetValueAtIndex(windows, i);
            CFStringRef key = CFStringCreateWithCString(NULL, "kCGWindowOwnerName", kCFStringEncodingUTF8);
            const void* value = nil;

            if (CFDictionaryGetValueIfPresent(windowDict, key, &value) == YES)
            {
                const char* c_value = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);

                NSLog(@"window: %s", c_value);
            }

            CFRelease(key);
        }
    }
    return 0;
}

免责声明 我对 Rust 也比较陌生,这可能不是惯用的解决方案

除了 TheNextman 的回答外,我还修改了代码以避免使用 transmute。

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!("window owner name: {}", result)
            }

        }

    }

    unsafe {
        CFRelease(window_list_info as CFTypeRef)
    }
}

以防万一您也遇到了这个问题。 kCGWindowOwnerName 的一些条目不是有效的简单 CFString,意味着在转换后你最终得到一个空指针。

要验证这一点,您可以运行这个小c程序:

#include <Carbon/Carbon.h>

// compile as such:
//  clang -framework carbon get-win.c -o get-win

int main(int argc, const char *argv[]) {
    CGWindowListOption options = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
    CFArrayRef windows = CGWindowListCopyWindowInfo(options, kCGNullWindowID);
    CFIndex count = CFArrayGetCount(windows);

    for (int i = 0; i < count; i++) {
        CFDictionaryRef windowDict = CFArrayGetValueAtIndex(windows, i);
        CFStringRef key = CFStringCreateWithCString(NULL, "kCGWindowOwnerName", kCFStringEncodingUTF8);
        const void *value = nil;

        if (CFDictionaryGetValueIfPresent(windowDict, key, &value) == 1) {
            const char *c_value = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
            if( c_value == NULL ) {
                // this is the very strange case, where a window owner name c_value is null, but `CFShow(value)`
                // yields a string, just without the regular quotation marks
                // like Finder vs "Finder"
                CFShow(value);
                CFShowStr(value);   // tells: This is an NSString, not CFString
            } else {
                printf("kCGWindowOwnerName = %s\n", c_value);
                printf("-- Details about the string:");
                CFShowStr(value);
                printf("-- End of Details\n\n");
            }
        }

        CFRelease(key);
    }
    CFRelease(windows);

    return 0;
}

我还没有弄清楚如何将 NSString* 转换为 CFString*char*。 如果您知道如何在 c 中执行此操作(不是 objective-c),请发表评论。